2011年9月10日土曜日

JDOで作成した単純なDataStoreをSlim3に移行してみた

GAEの値上げもあり、思い切ってSlim3に移行を試しましたのでその内容です。

通常Slim3使う場合はblankプロジェクトを使って作っていくようですが、もうすでにあるプロジェクトをSlim3に移行したかったので下記を参考にさせていただきました。
http://songofcloud.gluegent.com/2009/11/slim3-datastore2.html

試しに移行してみたのは2つのkindです。

---- Alert ---------移行前 自動でキーのIDが振られるタイプ
package com.hayato.dstest;

import javax.jdo.annotations.*;

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class Alert {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long alertId;
@Persistent
private String userId;
@Persistent
private String message;

public Alert(String userId, String message) {
this.userId = userId;
this.message= message;
}

public Long getAlertID() {
return alertId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message= message;
}
}

---- TestUser ---------移行前 StringのIDをキーで指定するタイプ
package com.hayato.dstest;

import javax.jdo.annotations.*;

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class TestUser {
@PrimaryKey
@Persistent
private String userId;
@Persistent
private String email;

public TestUser(String userId, String email) {
this.userId = userId;
this.email= email;
}

public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email= email;
}
}

JDOの場合は、次のような操作でした。

-----------作成
PersistenceManager pm = PMF.createManager();
TestUser user = new TestUser("123", "abc");
try {
pm.makePersistent(user);
} catch (Exception e) {
// error
} finally {
pm.close();
}

-----------取得、変更
PersistenceManager pm = PMF.createManager();
try {
TestUser user = pm.getObjectById(TestUser.class, "123");
user.setEmail("upd");
} catch (JDOObjectNotFoundException e) {
// not found
} finally {
pm.close();
}

-----------クエリ
PersistenceManager pm = PMF.createManager();
try {
Query query = pm.newQuery(Alert.class);
query.setFilter("userId == :pUserId");
List alerts = (List)query.execute("123");
for (Alert alert : alerts)
// 処理
} catch (Exception e) {
// error
} finally {
pm.close();
}

これを、なるべく手間をかけずに移行します。
まずはSlim3のjar等を設定します。これは一番上で紹介したサイトに載ってます。
で、kindを修正するわけですが、ここでプライマリキーの扱いについて注意が必要です。
JDOの方ではオートナンバーのLongや任意指定のStringをキーにできましたが、これをKey型に変更しないといけません。このKey型ですが、DataStore上でEntityを識別するための一意のKey値と、Long型の値もしくはString型の値の2つを保持します。

Key値
 ├──────┐
Long値 String値

こんな感じです。
今回の例ですと、AlertがLong値を使い、TestUserがString値を使うようにします。

これらを考慮して変更すると、

---- Alert ---------移行後 自動でキーのIDが振られるタイプ
package com.hayato.dstest;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.Model;
import com.google.appengine.api.datastore.Key;

@Model(kind = "Alert", schemaVersion = 1)
public class Alert {
@Attribute(primaryKey = true)
private Key alertId;
private String userId;
private String message;

public static Key createKey(Long alertId) {
return Datastore.createKey(Alert.class, alertId);
}

public Alert() {
}

public Alert(String userId, String message) {
this.userId = userId;
this.message= message;
}

public Key getAlertId() {
return alertId;
}
public void setAlertId(Key alertId) {
this.alertId = alertId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message= message;
}
}

---- TestUser ---------移行後 StringのIDをキーで指定するタイプ
package com.hayato.dstest;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.Model;
import com.google.appengine.api.datastore.Key;

@Model(kind = "TestUser", schemaVersion = 1)
public class TestUser{
@Attribute(primaryKey = true)
private Key userId;
private String email;

public static Key createKey(String userId) {
return Datastore.createKey(TestUser.class, userId);
}

public TestUser() {
}

public TestUser(String userId, String email) {
this.userId = createKey(userId);
this.email= email;
}

public Key getUserId() {
return userId;
}
public void setUserId(Key userId) {
this.userId = userId;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email= email;
}
}

こうなりました。ポイントとしては、プライマリキーがKey型に変更されたこと、もともとのLong値やString値からKeyオブジェクトを生成するためのcreateKeyというメソッドを追加したことです。
で、使い方は以下のように変わります。

-----------作成
try {
Alert alert = new Alert("123", "test message"); // <-Keyは指定してない
Datastore.put(alert);

TestUser user = new TestUser("123", "test@test.com"); // "123"がKey
Datastore.put(user);
} catch (Exception e) {
// error
}

-----------取得、変更
try {
Alert alert = Datastore.get(Alert.class, Alert.createKey(1L));
TestUser user = Datastore.get(TestUser.class, TestUser.createKey("123"));

// alertのプライマリキーを取得するには次のコード
Long alertId = alert.getAlertId().getId();
// TestUserのプライマリキーを取得するには次のコード この場合は"123"だけど
String userId = user.getUserId().getName();

alert.setMessage("update message");
Datastore.put(alert);
} catch (EntityNotFoundRuntimeException e) {
// not found
}

-----------クエリ
try {
AlertMeta alertMeta = AlertMeta.get(); // AlertMetaは、Slim3のジェネレータが自動作成したクラス
List alerts = Datastore.query(alertMeta)
.filter(alertMeta.userId.equal("123"))
.asList();
for (Alert alert : alerts) {
// 処理
}
} catch (Exception e) {
// error
}

これで一応動きました。まだ動いたというレベルですので何かしら問題がある可能性もあります。
動かしてみて気づいた点
・Key値を指定しないでputするとJDOのIdGeneratorStrategy.IDENTITYと同じ動きをするようだ。
・JDOではgetObjectByIdで取得してデータを変更するとDataStore上も変更されたが、Slim3では変更後にputしないと反映されない。