スティルハウスの書庫の書庫

はてなダイアリーで書いてた「スティルハウスの書庫」を移転してきました。

Datastore APIの取り扱いでハマる

(この記事は、アドビシステムズAdobe Developer Connection」向けに書いたものを再掲しています)

BlazeDSは動いたものの、GAE/Jにはもうひとつ落とし穴がありました。それは、GAE/JのデータストアであるBigtableにアクセスするためのAPI、「Datastore API」とBlazeDSの連携です。

Bigtableに保存されたデータを検索して取得するには、JDOで規定されたクエリ言語「JDOQL」を用いて、SQLライクなクエリを記述します。

		// クエリを作成
		PersistenceManager pm = pmf.getPersistenceManager();
		Query query = pm.newQuery(GtgCalendar.class);
		query.setFilter("token == tokenParam");
		query.declareParameters("String tokenParam");
		
		// クエリを実行
		List<GtgCalendar> results; 
		GtgCalendar gc = null;
		try {
			results = (List<GtgCalendar>)query.execute(token);
		} finally {
			query.closeAll();
			pm.close();
		}

ここでは、GtgCalendarのtokenフィールドをキーにして、同オブジェクトを検索しています。これもきわめて容易に記述できます。

eager loadingはできない

さて、Datastore APIHibernate等の一般のORMと異なる点は、「eager loadingできない」という点です。つまり、例えば1つのEntityクラス「Department」が複数のEntityクラス「Employee」と親子関係にあるとき、Hibernate等ではeager loading設定を行うことで、両テーブルを結合して「1件のDepartmentレコードとその下の10件のEmployeeレコード」を1回で取得することが可能です。

一方、RDBではないDatastore APIの場合はこれができません。よって、親オブジェクトから子オブジェクトをひとつひとつ参照していき、子オブジェクトをBigtableから読み出しておく必要があります。ご都合.comの例では、GtgCalendarの取得後、その子である複数のGtgResponse、さらに孫であるGtgAvailableRangeのフィールドを読み込むことで、必要なデータを手作業で読み込みしています(この実装はパフォーマンス的に改善の余地があるかもしれません)。

private void loadChildren(GtgCalendar gc) {
	for (GtgResponse gr : gc.getResponses()) {
		for (GtgAvailableRange gar : gr.getAvailableRanges()) gar.getKey();
	}
}

PersistenceManagerはクローズする

もうひとつ筆者がハマった点は、「必ずPersistenceManagerをクローズしてからオブジェクトをBlazeDSに渡す」という点です。PersistenceManagerは、Entityオブジェクトの保存に加えて、上述のような親オブジェクトから子オブジェクトへの参照取得(ナビゲーション)にともなうBigtableの読み込みを司ります。つまり、上記コードのfinally節にて記述されている「pm.close()」によってPersistenceManagerがクローズされるまでは、「EntityオブジェクトとPersistenceManagerがひも付いている状態」となります。

この状態のままEntityオブジェクトをBlazeDSのリモート呼び出しの戻り値として渡してしまうと、リモート呼び出し時に例外が発生してしまいます(これと同様のことはHibernateにてセッションをクローズしない場合にも起こります)。よって、リモート呼び出しの戻り値は、必ず上記finally節の外側で戻す必要があります。

主キーは「Key as Encoded String」にする

最後の落とし穴は、Entityクラスの主キーです。Datastore APIにて主キーを定義するもっとも簡単な方法は、long型のidフィールドを用意することです。そこで筆者は当初long型のidを用いていました。しかしこのタイプの主キーでは、上述したような親オブジェクトから子オブジェクトへのナビゲーションに対応していません。親から子へのナビゲーションを使うには、Bigtableにおける主キーを表す「com.google.appengine.api.datastore.Key」型のフィールドを設ける必要があります。

しかし、これでも不具合があります。このKey型のフィールドは、BlazeDSにてAMFのシリアライズを実施すると、キーの内容が失われてしまうのです。そこで筆者はさらに修正を加えて、Datastore APIのドキュメント上で「Key as Encoded String」と分類されている方法で主キーを実装しました。これは、上記のKeyオブジェクトの内容をString型にエンコードする方法で、主キーのフィールドには以下のようなアノテーションを追加します。

@Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
	private String key;

これによって、やっとEntityクラスの主キーを無事にFlexクライアントにも渡すことが可能となりました。

以上、本稿では「ご都合.com」の開発を通じて筆者が得た「GAE/J+BlazeDS」のtipsを紹介しました。ここで紹介した落とし穴さえ避ければ、この2つの技術のコンビはじつに強力な生産性を発揮します。ぜひ皆さんもお試しください。