Datastore APIの取り扱いでハマる
BlazeDSは動いたものの、GAE/Jにはもうひとつ落とし穴がありました。それは、GAE/JのデータストアであるBigTableにアクセスするためのAPI、「Datastore API」とBlazeDSの連携です。
GAE/J では、Java EE標準のJDO(Java Data Objects)をベースとする「Datastore API 」に基づいてコードを記述することで、BigTableを簡単にアクセスできます。例えば、ご都合.comにおいてユーザーが作成した個々のカレンダー情報は、GtgCalendarクラスというEntityクラスに保持されています。
@PersistenceCapable(identityType = IdentityType.APPLICATION) public class GtgCalendar { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true") private String key; @Persistent private String uid; @Persistent private String title; @Persistent private String comment; ...<以下、そのほかのフィールドやgetter/setterは省略>
このように、POJOクラスに「@Persistent」や「@PrimaryKey」といったアノテーションを記述することで、同クラスの内容を BigTableに保存可能になります。なお、BigTableでは事前にDBスキーマを定義しておく必要はなく、スキーマの変更も随時可能です。
このGtgCalendarに納められたカレンダー情報を保存するコードは、以下のように記述します(いずれもご都合.comで実際に動作しているコードです)。
// GtgCalendar「gc」を保存 PersistenceManager pm = pmf.getPersistenceManager(); try { pm.makePersistent(gc); } finally { pm.close(); }
このように、GtgCalenderをPersistenceManagerのmakePersistenceメソッドの引数として渡すだけで、その内容がBigTableに格納されます。ちょうどHibernateやRuby on RailsにおいてオブジェクトをDB保存する場合と同じ感覚で、ものの数行で記述できます。
一方、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 APIがHibernate等の一般の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(); } }