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ができない、というのは、この点をさす。

主キーによるFriendsInfoエンティティの取得

Friendsエンティティーを介さずに、主キーを合成して、(所望の)FriendsInfoエンティティーを取得できる。
主キーが「Rootからのパスとして表現されていること」が分かる。

JDOQLによるFriendsエンティティー、FriendsInfoエンティティーの取得

以下の画面を得た。問題はなし。



Friendsエンティティーを全て削除(カスケード削除)

これを実行したところ、Friendsエンティティーとともに、FriendsInfoエンティティーが削除された。
カスケード削除される、ということ。