1対多の所有関係にあるデータを記録するサンプル
Google App EngineのDatastoreで1対多の所有関係にあるデータを相互参照できる形で記録するサンプルです。
- 1対多
- 1つの親が、複数の子を持つような関係です。
親(Parent.class) ├子1(Child.class) ├子2(Child.class) └子3(Child.class)
- 所有関係
- オブジェクトの所有される側(子)は所有者(親)を必ず必要とする強い関係?です。
- もうひとつ、非所有関係というのもあり、
- これであれば「どちらのオブジェクトもお互いの関係から独立して存在することができる」とのこと。
- 多対多の関係を構築する場合は、非所有関係を使うらしい。
- なお、非所有関係のデータは1トランザクションでは保存できない、という制約があります。
- 相互参照
- 親/子の双方で検索できるようにします。具体的には以下の両方ができる、ということです。
- 親Aが保持する子の一覧を得る。
- 子Bを保持する親を取得する。
- 親/子の双方で検索できるようにします。具体的には以下の両方ができる、ということです。
モデルクラスの定義
まずは、永続化するモデルクラスを定義します。
親(Parent.class)
- 子を保持するフィールド用意します。
- プライマリキー(ID)はLongでOKです。
import java.util.ArrayList; import java.util.List; 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; /** * 親 */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Parent { /** * 親のID */ @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id; @Persistent private String name = ""; /** * 子を保持するフィールド * mappedByで子が持つ親のフィールド名を指定する。 */ @Persistent(mappedBy = "parent") private List<Child> children = new ArrayList<Child>(); public Parent( String name ) { this.name = name; } // アクセサ public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name;} public List<Child> getChildren () { return children;} public void setChildren (List<Child> child) { this.children = child;} public void addChild( Child child ) { this.children.add(child); } }
子(Child.class)
- プライマリキー(ID)をKeyにします。
- 「親キーの情報を保存できるようにするため」とのこと。
- 親の参照を格納するフィールドを用意します。
- 子から親をたどるときはこのフィールドを参照します。
- 値は、Datastoreが親で指定されたmappedByの値を元に自動で記録/設定してくれるらしい。
- プロキシパターンになっていて、フィールドにアクセスした際に内部的にデータストアにアクセスし親を取り出して返す、と書いてあったような。
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; /** * 子 */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Child { /** * プライマリーキー * Keyにしておく必要がある。 */ @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key id; @Persistent private String name = ""; /** * 親の参照を格納するフィールド。 */ @Persistent private Parent parent = null; public Child( String name ) { this.name = name; } // アクセサ public Key getId() { return id; } public void setId(Key id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name;} public Parent getParent() { return parent;} public void setParent(Parent parent) { this.parent = parent;} }
データの登録
データ登録は、親をnewして子を追加したあと、PersistenceManager#makePersistent()するだけ。
public void insertData( ) { add( "aa", new String[]{ "cc","dd","ee" }); add( "bb", new String[]{ "ff","cc" }); } @Tx.Persistence void add( String parent, final String[] children ) { // 親 Parent p = new Parent( parent ); for ( String name : children ) { // 子を追加 p.addChild( new Child( name ) ); } // 永続化(Txインターセプタを利用) Tx.getPersistenceManager().makePersistent( p ); }
トランザクションはこの前作成したトランザクションインターセプタを利用しています。なお、複数のエンティティを1トランザクションで同時に作成することは不可なので、インターセプタは↑の例ではadd()に設定する必要があります。(insertDataに適用すると、登録時にエラーになる)
データの取り出し
参照も特に特殊な処理は必要なく、
- データのインスタンスを取得して、
- ParentやChildに設定した親or子のフィールドを参照
すればOKです。
親が保持する子の一覧を得る
PersistenceManager pm = Tx.getPersistenceManager(); // 名前で親を探索 Query query = pm.newQuery(Parent.class); query.setFilter("name == p_name"); query.declareParameters("String p_name"); List<Parent> list = (List<Parent>) query.execute( "aa" ); // 条件にマッチする親の一覧 for ( Parent p : list ) { // 親が取得できたら、後は親の定義した子を保持するフィールドを参照するだけでOK for( Child c : p.getChildren() ) { ... } }
子を持つ親を参照する
PersistenceManager pm = Tx.getPersistenceManager(); // 名前で子を探索 Query query = pm.newQuery(Child.class); query.setFilter("name == p_name"); query.declareParameters("String p_name"); List<Child> list = (List<Child>) query.execute( "cc" ); // 条件にマッチする子の一覧 for ( Child c : list ) { // 子が取得できたら、後は親のフィールドを参照するだけでOK Parent parent = c.getParent(); }