2009/05/23

GAE/JのDatastoreのはなしとか

なんとなくまとめ。使い込んでないので突っ込みお待ちしてます。

キー

まず、保存したいオブジェクト(Javaオブジェクト:POJO)はオブジェクトとかインスタンスとかいいます。Datastore内に保存されているものはエンティティといいます。(リレーショナルDBならタプルとかレコードとか)。

で、エンティティはDatastore内で一意なキーをもつ必要があって、オブジェクトの対象のフィールドに@PrimaryKeyアノテーションをつけて識別します。

Datastoreで使えるキーは4種類あって、単純なLong、String、そしてKeyとそれをStringにエンコードしたEncoded Key Stringって書いてあるヤツ。

LongだとDatastoreに保存したときに自動的に採番されます。設定もできるみたい。Stringはアプリケーションが勝手につけることができます。例えばE-mailで一意に識別したいとか。

残りはKeyクラスとEncoded Key String。関連を使うときにこいつらを使います。Keyはいくつかの情報をもっていて、1つ目はエンティティの種類(RDBならテーブルみたいなもの。Employee.class.getSimpleName()みたいな感じで取得したJavaのクラス名)。

2つ目はIDで、上記のLongかStringをキーにしたときの値。何も設定しない場合はLongと同じく自動採番されるし、Stringで設定する場合は、KeyFactory.createKey(Employee.class, "キーにしたい文字列")って感じでKeyを作れます。

3つ目は親エンティティの情報。Ownedリレーションで説明します。

リレーション

RDBではおなじみの関連。JDOではOwned RelationshipとUnowned Relationshipの2種類があって、Owned Relationshipは所有関係リレーション、Unowned Relationshipは非所有関係リレーションとでも言えばいいのかな。それぞれ1対1、1対多、多対多があるのだけど、GAE/J版ではOnwnedは1対1、1対多だけ、Unownedはまだ対応が弱いとのことで、何でもできるけど自前で頑張らないといけないことが多い。

Owned

Ownedの場合、親エンティティと子エンティティは互いに独立して存在することができない。GAEのページにある例でいうと、ContactInfo(子)はEmployee(親)がないと存在できない。親を消すと子も消える。

で、子エンティティのキーには、親エンティティの情報を持たせなければならないっぽい。それが、子のキーは、親エンティティのキーをエンティティグループの親として使うっていう意味らしい。

だから、子エンティティにしたいオブジェクトのPrimaryKeyフィールドは、Keyクラスか、Encoded Key Stringにしないとダメ。で、子エンティティを作るときには子エンティティのキーに、親エンティティのKey情報をいれるので、先に親がないとダメってこと。

サンプルにあるPerson has one(またはmany) Favorite Foodみたいな関連で、PersonとFoodが独立して存在する必要がある場合は、OwnedではなくUnownedにする必要があるみたいです。例えばFood(餃子)が好きな人は複数人いる可能性があるので、Food(餃子)のKeyに特定の人のKeyを入れるわけにはいかない。

Ownedにする利点は、EmployeeクラスにContactInfo型のcontactInfoフィールドを追加しておけば、Employeeクラスのエンティティを取得して、contactInfoフィールドにアクセスしたときに自動的にその情報がDatastoreから読み込まれるし、ContactInfoをいじった場合に、Employeeを保存すればContactInfoも一緒に更新される。あとトランザクションを使えるとか。

Unowned

上のPersonとFoodみたいな関連の場合、Unownedリレーションにする必要があるのだけど、現在のGAE/J用JDOではPersonクラスにFood型のフィールドをもたせて対応ができない。どうするかというと対象のFoodのキーを保存するKeyかStringなフィールドを持たせることになる。RDBでの外部キーみたいな感じ。この場合、FoodのキーはKeyクラスじゃなくLongとかでもOK。

この場合、Personを保存したときに一緒に保存されるのはFoodのKeyだけ。Personを読み込んだときにfavoriteFoodのエンティティを読み込むのは自動ではできない。いちいちPerson#getFavoriteFood()とかつくって、return pm.getObjectById(favoriteFood)見たいな感じで自前でロードする必要があるようです。メンドイ。

おまけにPersonとFoodをトランザクション内で一緒に操作するには、同じエンティティグループに入れなきゃいけない。エンティティグループはあんまり調べてないのでまた別途。

検索のしかた

Queryの制限に書いてある通り、OwnedでもUnownedでもJoinというか、関連エンティティの属性を使って検索ができない。例えば、Foodにnameという餃子とかカレーとか食べ物の名前を入れられるフィールドがあるとしよう。で、Person.favoriteFood.nameが餃子な人を検索するといったことができない。

そんな検索するには、一回Foodを検索して、Keyを取得する。で、改めて、PersonでfavoriteFoodと取得したKeyが一致するのを検索する。そんな例を書いて今日はお別れです。メンドイ人は、非正規化して食べ物の名前をPersonに持たしちゃえばいいよ!

// 食べ物クラス package example; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Food { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private String name; public Food(String name) { this.name = name; this.key = KeyFactory.createKey(Food.class.getSimpleName(), this.name); } public Key getKey() { return key; } public String getName() { return name; } } //人間クラス package example; import javax.jdo.PersistenceManager; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private String name; @Persistent private Key favoriteFood; public Person(String name, Food favoriteFood) { this.name = name; this.key = KeyFactory.createKey(Person.class.getSimpleName(), this.name); this.favoriteFood = favoriteFood.getKey(); } public Key getKey() { return key; } public String getName() { return name; } public Food getFavoriteFood() { PersistenceManager pm = PMF.get().getPersistenceManager(); try { return (Food) pm.getObjectById(Food.class, favoriteFood); } finally { pm.close(); } } } テストケース public void testFavoriteFood() { //どこかで作っておく。 Food gyouza = new Food("餃子"); PersistenceManager pm = PMF.get().getPersistenceManager(); pm.makePersistent(gyouza); pm.close(); Person a = new Person("餃子好きなAさん", gyouza); Person b = new Person("餃子好きなBさん", gyouza); pm = PMF.get().getPersistenceManager(); pm.makePersistent(a); pm.makePersistent(b); pm.close(); //どこかで、餃子好きなひとを探したくなった! //この例だと、Foodのキーを名前にしているので //createKeyしたキーで検索すればよくて //わざわざFoodを検索する必要はないんだけどね。。。 pm = PMF.get().getPersistenceManager(); Key gyouzaKey = KeyFactory.createKey(Food.class.getSimpleName(), "餃子"); Food food = (Food) pm.getObjectById(Food.class, gyouzaKey); Query q = pm.newQuery(Person.class); q.setFilter("favoriteFood == food"); q.declareParameters("com.google.appengine.api.datastore.Key food"); List gyouzaSuky = (List) q.execute(food.getKey()); assertEquals(2, gyouzaSuky.size()); assertEquals("餃子好きなAさん", gyouzaSuky.get(0).getName()); assertEquals("餃子", gyouzaSuky.get(0).getFavoriteFood().getName()); }

1 件のコメント:

匿名 さんのコメント...

分かりやすくて参考になりました!
すっごく感謝!!
  サエキ