Google App Engine/JavaのData Storeで所有関係を作ってみる(1:N)
前回のログに引き継き、今度はGAE/Jで1:Nの所有関係を作ってみた。
1:Nの所有関係も、基本的には1:1の時と同じ。GAEのマニュアルを読む限り、所有側のエンティティーにコレクション型のプロパティーを作成すればよい(サポートされるコレクションは、これ)。
今回は、List型のプロパティーを(前回作ったFriends.java)に追加してみる。
以下の画面から、
- List型プロパティーへ被所有側のエンティティ(前回作ったFriendInfo.java)の参照を代入して、所有者をストアすることで、一気に被所有者もストアする。
- このとき、被所有者の主キーが、「親からのパスを含むキー」として自動的に生成される。
- 所有者を削除することで、非所有者も一緒に削除する(カスケード削除、リカーシブ削除)
を確認する。
驚いたことに、(殆ど)同じコードなのにカスケード削除ができてしまった。
やっぱり、前回のコードが悪いのか、それとも、そういう仕様なのか分からないが。。。。
プロジェクトの作成
前回のサンプルと殆ど同じ(サーブレット数と名前の変更をしない)ので、Eclipse上でプロジェクトをコピーしてしまう。
バージョンを変えたければ、プロジェクトを選択し、「Google」=>「Apps Engine Settings」でバージョンを変更する。
エンティティーの変更
前回のサンプルで作った3つのエンティティー(RootTO.java、Friends.java、FriendsInfo.java)のうち、Friends.javaだけ変更する。
変更点は、
List<FriendsInfo> friendsInfoList
というプロパティーを追加するだけである。
Friends.java
||<package test.entities; import java.util.Date; import java.util.List; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.PrimaryKey; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.Extension; import com.google.appengine.api.datastore.Key; /********************************* * * エンティティー(所有) * * @author * tetsuya_odaka (EzoGP) <br> *********************************/ @PersistenceCapable(identityType=IdentityType.APPLICATION) public class Friends { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent @Extension(vendorName="datanucleus",key="gae.parent-pk",value="true") private Key parentKey; @Persistent private String firstName; @Persistent private String lastName; @Persistent private List<FriendsInfo> friendsInfoList; @Persistent private Date entryDate; /** * コンストラクタ * */ public Friends(String firstName, String lastName,Date entryDate) { this.firstName = firstName; this.lastName = lastName; this.entryDate = entryDate; } /** * setter/getter */ public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Date getEntryDate() { return entryDate; } public void setEntryDate(Date entryDate) { this.entryDate = entryDate; } public List<FriendsInfo> getFriendsInfoList() { return friendsInfoList; } public void setFriendsInfoList(List<FriendsInfo> friendsInfoList) { this.friendsInfoList = friendsInfoList; } public Key getKey() { return key; } public void setKey(Key key) { this.key = key; } public Key getParentKey() { return parentKey; } public void setParentKey(Key parentKey) { this.parentKey = parentKey; } }
Friendsエンティティーと、FriendsInfoエンティティーのストア
Friends.javaに上記の変更を施したところで、Friendsエンティティーをストアする部分のコードを変更する。
以下のように変更すれば、Friendsエンティティー(所有者)をストアしたタイミングで、FriendsInfoエンティティー(被所有者)のエンティティーもストアされるはず。
GaeDataStoreCreateChildrenServlet.java
package test; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.jdo.PersistenceManager; import javax.servlet.http.*; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import test.entities.Friends; import test.entities.FriendsInfo; import test.entities.RootTO; @SuppressWarnings("serial") public class GaeDataStoreCreateChildrenServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { /** * 所有されるエンティティの生成 */ FriendsInfo fi1_1 = new FriendsInfo("属性1の1",new Date()); FriendsInfo fi1_2 = new FriendsInfo("属性1の2",new Date()); FriendsInfo fi2_1 = new FriendsInfo("属性2の1",new Date()); FriendsInfo fi2_2 = new FriendsInfo("属性2の2",new Date()); FriendsInfo fi3_1 = new FriendsInfo("属性3の1",new Date()); FriendsInfo fi3_2 = new FriendsInfo("属性3の2",new Date()); // friendsキーの生成(1_1) KeyFactory.Builder kb = new KeyFactory.Builder(FriendsInfo.class.getSimpleName(), "info1_1"); Key key1_1 = kb.getKey(); // friendsキーのセット(1_1) fi1_1.setKey(key1_1); // friendsキーの生成(1_2) kb = new KeyFactory.Builder(FriendsInfo.class.getSimpleName(), "info1_2"); Key key1_2 = kb.getKey(); // friendsキーのセット(1_1) fi1_2.setKey(key1_2); List<FriendsInfo> list1 = new ArrayList<FriendsInfo>(); list1.add(fi1_1); list1.add(fi1_2); // friendsキーの生成(2_1) kb = new KeyFactory.Builder(FriendsInfo.class.getSimpleName(), "info2_1"); Key key2 = kb.getKey(); // friendsキーのセット(2_1) fi2_1.setKey(key2); // friendsキーの生成(2_2) kb = new KeyFactory.Builder(FriendsInfo.class.getSimpleName(), "info2_2"); Key key2_2 = kb.getKey(); // friendsキーのセット(2_1) fi2_2.setKey(key2_2); List<FriendsInfo> list2 = new ArrayList<FriendsInfo>(); list2.add(fi2_1); list2.add(fi2_2); // friendsキーの生成(3_1) kb = new KeyFactory.Builder(FriendsInfo.class.getSimpleName(), "info3_1"); Key key3_1 = kb.getKey(); // friendsキーのセット(3_1) fi3_1.setKey(key3_1); // friendsキーの生成(3_2) kb = new KeyFactory.Builder(FriendsInfo.class.getSimpleName(), "info3_2"); Key key3_2 = kb.getKey(); // friendsキーのセット(3_2) fi3_2.setKey(key3_2); List<FriendsInfo> list3 = new ArrayList<FriendsInfo>(); list3.add(fi3_1); list3.add(fi3_2); PersistenceManager pm = PMF.get().getPersistenceManager(); /** * 所有する側のエンティティの生成 */ Friends child1 = new Friends("次郎","鈴木",new Date()); Friends child2 = new Friends("太郎","佐藤",new Date()); Friends child3 = new Friends("三郎","山田",new Date()); // キーの生成(1) kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "parentKey"); kb.addChild(Friends.class.getSimpleName(), "friend1"); Key key4 = kb.getKey(); // キーのセット(1) child1.setKey(key4); // キーの生成(2) kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "parentKey"); kb.addChild(Friends.class.getSimpleName(), "friend2"); Key key5 = kb.getKey(); // キーのセット(2) child2.setKey(key5); // キーの生成(3) kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "parentKey"); kb.addChild(Friends.class.getSimpleName(), "friend3"); Key key6 = kb.getKey(); // 子のキーのセット(3) child3.setKey(key6); // FriendsInfoListのセット // (注)こうしてセットしてmakePersistentすれば、エンティティー // が生成され、キーには親のキーが負荷される。(親キーもセットされる。entity beanの設定より) child1.setFriendsInfoList(list1); child2.setFriendsInfoList(list2); child3.setFriendsInfoList(list3); boolean flag=false; try{ pm.makePersistent(child1); pm.makePersistent(child2); pm.makePersistent(child3); flag=true; }finally{ pm.close(); } resp.setContentType("text/html; charset=utf-8"); PrintWriter out = resp.getWriter(); // 出力部 out.println("<html><head>"); out.println("<title>Friends,List<FriendsInfo>エンティティーの登録</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>Friends,List<FriendsInfo>エンティティーの登録</h2>"); if(flag){ out.println("Friends,List<FriendsInfo>エンティティーを登録しました。"); }else{ out.println("Friends,List<FriendsInfo>エンティティーの登録に失敗しました。"); } out.println("</body></html>"); } }
主キーによるFriendsエンティティーの取得
Friends.javaを変更したので、主キーをもとにFriendsエンティティーを(Data Storeから)取得し、そのプロパティーと、被所有のFriendsInfoエンティティーのプロパティーを参照するコードを変える。
GaeDataStoreGetFriendsServlet.java
package test; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; import javax.jdo.PersistenceManager; import javax.servlet.http.*; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import test.entities.Friends; import test.entities.FriendsInfo; import test.entities.RootTO; @SuppressWarnings("serial") public class GaeDataStoreGetFriendsServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { PersistenceManager pm = PMF.get().getPersistenceManager(); // キーの生成 KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "parentKey"); kb.addChild(Friends.class.getSimpleName(), "friend2"); Key key = kb.getKey(); // オブジェクトの取得 Friends friend = pm.getObjectById(Friends.class, key); String firstName = friend.getFirstName(); String lastName = friend.getLastName(); String keyString = friend.getKey().toString(); String parentKeyString = friend.getParentKey().toString(); // ArrayListで戻ってくる List<FriendsInfo> friendsInfoList = friend.getFriendsInfoList(); resp.setContentType("text/html; charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("<html><head>"); out.println("<title>Friendsエンティティーの取得</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>キーによるFriendsエンティティーの取得</h2>"); out.println("<table border=1>"); out.println("<tr><td>種類</td><td>"+Friends.class.getSimpleName()+"</td></tr>"); out.println("<tr><td>主キー</td><td>"+keyString+"</td></tr>"); out.println("<tr><td>親キー</td><td>"+parentKeyString+"</td></tr>"); out.println("<tr><td>名</td><td>"+firstName+"</td></tr>"); out.println("<tr><td>姓</td><td>"+lastName+"</td></tr>"); Iterator<FriendsInfo> it = friendsInfoList.iterator(); while(it.hasNext()){ FriendsInfo friendsInfo = (FriendsInfo) it.next(); out.println("<tr><td>属性名</td><td>"+friendsInfo.getAttrName()+"</td></tr>"); } out.println("</table></body></html>"); // PersistenceManagerはFriendsInfoをListからFetchした後にクローズする。 pm.close(); } }
デプロイと確認
以上で、コードの修正は終わり。
ローカル環境で一通りのテストをして(Indexを作成するため)、GAEサーバーへデプロイする。
以下はテスト結果
主キーによるFriendsエンティティの取得
以下の画面を得た。
取得する側のプログラムで主キーを生成して、(所望の)Friendsエンティティーをストアから取得できること、Listとして保持した被所有のエンティティー(FriendsInfo)もストアから取得でき、プロパティーを参照できる。
(注記)GAEのマニュアルによれば、FriendsInfoは呼び出されたタイミングでストアからロードされる。eager loadができない、というのは、この点をさす。