SimpleDB を紹介するこの連載の前半では、Amazon 独自の API を使用して CRUD スタイルのレース参加アプリケーションをモデル化する方法を説明しました。ほとんどの Java 開発者にとって、データ型にストリングだけを使うという Amazon の手法は明らかに特異なものですが、それは別としても、Amazon API に懐疑的な思いを抱いた方はいることでしょう。しかし結局のところ、リレーショナル・データベースを利用するための API は今ではかなり標準的で十分に検討された API になっており、もっと重要なことは皆さんが使い慣れてきていることです。
最近のリレーショナル・フレームワークの多くには、Java Persistence API が実装されているため、ほとんどのタイプの Java アプリケーションでは、RDBMS でドメイン・オブジェクトをモデル化するのが簡単で、お馴染みの作業となっています。ドメインを上手くモデル化する方法をすでにマスターしているというのに、さらに新しい方法を学ぶのには抵抗を感じて当然です。幸い、SimpleDB では新しい方法を学ぶ必要はありません。
この SimpleDB を紹介する連載の後半では、第 1 回のレース参加アプリケーションをリファクタリングして JPA 仕様に準拠させる方法を説明します。その後、このアプリケーションを SimpleJPA に移植して、この革新的なオープンソースのプラットフォームが NoSQL のドメイン・モデリングへの順応、そしてクラウド・ベースのストレージの利用を多少なりとも容易にするいくつかの方法に目を向けます。
最近の Java 開発者の多くは、データ・パーシスタンスに Hibernate (および Spring) を利用しています。Hibernate はオープンソースの成功例の先駆けであるだけでなく、ORM の分野も改善しました。Hibernate が登場するまで、Java 開発者は EJB エンティティー Bean の泥沼と格闘しなければなりませんでした。さらにそれ以前は、基本的に独自の ORM に取り掛かるか、IBM® のようなベンダーから ORM を購入していましたが、Hibernate は現在の多くの開発者たちが当然と思っている POJO ベースのモデリング・プラットフォームを支持し、ORM の複雑さとコストを一掃しました。
POJO をデータ・モデリングに利用するという Hibernate の新しい手法が人気を得たことを受けて、JPA (Java Persistence API) は作成されました。現在、EJB 3.0 でも、Google App Engine でも JPA を実装しています。Hibernate の EntityManager を使用しているとしたら、Hibernate 自体も JPA 実装の 1 つとして数えられます。
今や、Java 開発者が POJO を使用してデータ中心のアプリケーションをモデル化するのにいかに慣れ親しんでいるかを考えると、SimpleDB のようなデータ・ストアでも同じような手段を提供するのが道理です。突き詰めるところ、データ・ストアはデータベースのようなものだと言えます。
SimpleJPA を使用するには、Race オブジェクトと Runner オブジェクトに多少手を加えて JPA 仕様に合わせる必要があります。有難いことに、JPA の基本は至って簡単で、通常の POJO をアノテーションで修飾すれば、あとは EntityManager 実装が処理してくれます。つまり、XML は一切必要ありません。
JPA で使用する主なアノテーションは、@Entity と @Id の 2 つです。前者は POJO を永続オブジェクトとして指定し、後者はオブジェクトの ID キーを明確にします。レース参加アプリケーションを JPA に変換する場合には、関係を管理するために使用する 2 つのアノテーション、@OneToMany と @ManyToOne も必要になります。
この連載の前半では、ランナーとレースを永続化する方法を説明しましたが、これらのエンティティーを表すためのオブジェクトは全く利用しませんでした。Amazon の API をそのまま使用して、両方のプロパティーを存続させただけにすぎません。レースとそのレースに参加するランナーとの間の単純な関係をモデル化するとしたら、リスト 1 に記載するようにモデル化することができます。
リスト 1. 単純な Race オブジェクト
public class Race {
private String name;
private String location;
private double distance;
private List<Runner> runners;
//setters and getters left out...
}
|
リスト 1 では、4 つのプロパティーを持つ Race オブジェクトを指定しています。最後のプロパティーは、ランナーの Collection です。ランナーの名前 (今のところは極めて単純にしておきます) と SSN、そしてそのランナーが参加する Race インスタンスを保持する単純な Runner オブジェクトは、リスト 2 のように作成することができます。
リスト 2. Race に関連付けられた単純な Runner
public class Runner {
private String name;
private String ssn;
private Race race;
//setters and getters left out...
}
|
上記のリスト 1 とリスト 2 を見るとわかるように、ランナーとレースの多対 1 の関係は論理的にモデル化されています。現実の世界では、多対多のリンクを作ったほうが適切だと思いますが (普通、ランナーは複数のレースに参加するものです)、ここでは複雑にならないよう、このままにしておきます。また、今のところはコンストラクター、セッター、ゲッターを省略してありますが、これらについては後で説明します。
この 2 つのオブジェクトを SimpleJPA で使えるようにするのは、さほど難しいことではありません。まずは、@Entity アノテーションをそれぞれのオブジェクトに追加することで、オブジェクトを永続化可能にするという意図を明らかにします。さらに、Race オブジェクトでは @OneToMany を使用し、Runner オブジェクトでは @ManyToOne を使用して、オブジェクト間の関係を適切に表現する必要があります。
@Entity アノテーションはクラス・レベルで追加し、関係を示すアノテーションはゲッター・レベルで追加します。以上の追加内容をリスト 3 とリスト 4 に記載します。
リスト 3. JPA のアノテーションを追加した Race オブジェクト
@Entity
public class Race {
private String name;
private String location;
private double distance;
private List<Runner> runners;
@OneToMany(mappedBy = "race")
public List<Runner> getRunners() {
return runners;
}
//other setters and getters left out...
}
|
リスト 3 では、getRunners メソッドを @OneToMany アノテーションで修飾しました。また、関係を検出するために使用するプロパティーとしては、Runner エンティティーの race プロパティーを指定しています。
リスト 4 でも同じようにして Runner オブジェクトの getRace メソッドにアノテーションを付けます。
リスト 4. JPA のアノテーションを追加した Runner オブジェクト
@Entity
public class Runner {
private String name;
private String ssn;
private Race race;
@ManyToOne
public Race getRace() {
return race;
}
//other setters and getters left out...
}
|
大抵のデータ・ストアには (リレーショナルでも、そうでなくても)、データの一意性を表す何らかの方法が必要です。つまり、この 2 つのオブジェクトをデータ・ストアに永続化するには、少なくとも ID をオブジェクトに追加しなければなりません。そこで、リスト 5 では BigInteger 型の id プロパティーを Race ドメイン・オブジェクトに追加しています。Runner に対してもこれと同じ処理を行います。
リスト 5. Race オブジェクトへの ID プロパティーの追加
@Entity
public class Race {
private String name;
private String location;
private double distance;
private List<Runner> runners;
private BigInteger id;
@Id
public BigInteger getId() {
return id;
}
@OneToMany(mappedBy = "race")
public List<Runner> getRunners() {
return runners;
}
//other setters and getters left out...
}
|
リスト 5 の @Id アノテーションは、ID の管理方法に関する情報を何も提供していません。そのため、プログラムは、例えば EntityManager を使用して ID が管理するのではなく、手動で ID の管理が行われることを前提にします。
これまでのところ、SimpleDB に固有の作業は何も行っていません。Race および Runner オブジェクトには JPA アノテーションを使って一般的にアノテーションを付けているため、JPA 実装がサポートするデータ・ストアであれば、どのデータ・ストアにもこれらのオブジェクトを永続化することができます。これに該当するデータ・ストアには、Oracle、DB2、MySQL、そして (もうおわかりだと思いますが) SimpleDB があります。
SimpleJPA は、Amazon の SimpleDB のためのオープンソースの JPA 実装です。SimpleJPA は JPA 仕様のすべてをサポートするわけではありませんが (例えば、JPA クエリーでの結合はできません)、その大部分をサポートするので調べる価値はあります。
SimpleJPA を使用することによってもたらされる大きなメリットは、前回の記事で説明した辞書式順序の問題に、SimpleJPA はシームレスに対処しようとすることです。オブジェクトが数値型を使っている場合、SimpleJPA はストリングに変換し、その後のパディング処理を必要に応じて行います。つまり、String 型を反映させるためにドメイン・モデルを変更する必要はほとんどないということです (この後すぐに説明するように、1 つの例外としてドメイン・モデルを変更しなければならない場合があります)。
JPA 実装である SimpleJPA では、JPA 準拠のドメイン・オブジェクトを簡単に使用することができます。SimpleJPA を使用するための要件は唯一、String 型の ID を使用することだけです。したがって、id プロパティーは java.lang.String でなければなりません。簡単のため、SimpleJPA では基底クラス IdedTimestampedBase を用意しています。この基底クラスはドメイン・オブジェクトの ID プロパティーを管理するだけでなく、日付属性の created および updated も管理します (裏では、SimpleDB が一意の Id を生成します)。
Race クラスと Runner クラスを SimpleJPA に準拠させるには、SimpleJPA の便利な基底クラスを継承するという方法、または各クラスの id プロパティーを BigInteger から String に変更するという方法のいずれかを使用することができます。私が選んだのは、前者の方法です (リスト 6 を参照)。
リスト 6. SimpleJPA の IdedTimestampedBase 基底クラスを使用するように Race を変更する
@Entity
public class Race extends IdedTimestampedBase{
private String name;
private String location;
private double distance;
private List<Runner> runners;
@OneToMany(mappedBy = "race")
public List<Runner> getRunners() {
return runners;
}
//other setters and getters left out...
}
|
Runner の場合のコードは記載しませんが、遠慮なく自分で作成してみてください。IdedTimestampedBase を継承して、id プロパティーを Runner から削除するだけの話です。
レース参加アプリケーションを SimpleJPA に準拠させるための最初のステップは、Race と Runner の ID を更新することです。次に、オブジェクトのプリミティブ・データ型 (double、int、float など) を Integer や BigDecimal などのオブジェクトに変える必要があります。
まずは Race の distance プロパティーから取り掛かることにします。私の経験から、BigDecimal は (現行の SimpleJP リリースでは) Double よりも信頼できると思うので、Race の distance プロパティーは BigDecimal に変更しました (リスト 7 を参照)。
リスト 7. distance を BigDecimal に変更する
@Entity
public class Race extends IdedTimestampedBase{
private String name;
private String location;
private BigDecimal distance;
private List<Runner> runners;
@OneToMany(mappedBy = "race")
public List<Runner> getRunners() {
return runners;
}
//other setters and getters left out...
}
|
これで、Runner と Race の両方を SimpleJPA 実装で永続化する準備が整いました。
SimpleDB を対象に SimpleJPA でドメイン・オブジェクトを操作するのは、通常のリレーショナル・データベースを対象に JPA 実装を使用する場合と何ら変わりはありません。JPA を使用してアプリケーション開発を行った経験があれば、何も驚くべき点はないはずです。唯一、目新しいことと言えば、SimpleJPA の EntityManagerFactoryImpl を構成することくらいで、それには Amazon Web サービスのクレデンシャルと、SimpleDB ドメインのプレフィックス名が必要になります (あるいは、クレデンシャルが含まれるプロパティー・ファイルをクラス・パスに設定するという方法もあります)。
SimpleJPA の EntityManagerFactoryImpl のインスタンスを作成するときに指定したプレフィックス名を使用すると、SimpleDB ドメインはそのプレフィックスで始まり、その後にダッシュ、次にドメイン・オブジェクト名が続くことになります。したがって、「b50」というプレフィックスを指定した場合、SimpleDB で Race アイテムを作成すると、「b50-Race」というドメインになります。
SimpleDB の EntityManagerFactoryImpl のインスタンスを作成すると、あとはすべてインターフェース任せとなります。ここで必要となる EntityManager インスタンスは、EntityManagerFactoryImpl から取得します (リスト 8 を参照)。
リスト 8. EntityManager を取得する
Map<String,String> props = new HashMap<String,String>();
props.put("accessKey","...");
props.put("secretKey","..");
EntityManagerFactoryImpl factory =
new EntityManagerFactoryImpl("b50", props);
EntityManager em = factory.createEntityManager();
|
EntityManager のハンドルを手に入れさえすれば、あとは意のままにドメイン・オブジェクトを操作することができます。例えば、以下のような Race インスタンスを作成することが可能です。
リスト 9. Race の作成
Race race = new Race();
race.setName("Charlottesville Marathon");
race.setLocation("Charlottesville, VA");
race.setDistance(new BigDecimal(26.2));
em.persist(race);
|
リスト 9 では、SimpleJPA がすべての HTTP リクエストを処理してクラウド内に Race を作成します。SimpleJPA を使用するということは、リスト 10 に記載するように、JPA クエリーを使用してレースを取得できるということです (これらのクエリーでは結合を行えませんが、数値での検索は引き続き可能です)。
リスト 10. 距離によるレースの検索
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
List<Race> races = query.getResultList();
for(Race race : races){
System.out.println(race);
}
|
SimpleJPA の内部で行われる数値からストリングへの変換は、殊のほか役立ちます。例えば SimpleJPA でクエリーの出力を有効にすると、SimpleJPA が SimpleDB に対して発行するクエリーの内容を確認することができます。送信されたクエリーをリスト 11 に記載するので、distance がどのようにエンコードされているかに注目してください。
リスト 11. SimpleJPA が数値を巧みに処理してくれます!
amazonQuery: Domain=b50-Race, query=select * from `b50-Race` where `distance` = '0922337203685477583419999999999999928946' |
自動パディング処理とエンコードによって、大幅に手間が省けると思いませんか?
SimpleDB ではクエリーでのドメイン結合を許可していないとは言え、複数のドメインで関連アイテムを使用することは可能です。第 1 回で説明したように、関連オブジェクトのキーを別のオブジェクトに保管するだけで、そのオブジェクトを必要なときにいつでも取得できるようになります。SimpleJPA でも同じことを行います。例えば、前に JPA アノテーションを使って複数の Runner を Race にリンクする方法を紹介しました。つまり、Runner のインスタンスを作成して、そのインスタンスに既存の race インスタンスを追加すれば、Runner インスタンスを永続化することができるというわけです (リスト 12 を参照)。
リスト 12. SimpleJPA での関係
Runner runner = new Runner();
runner.setName("Mark Smith");
runner.setSsn("555-55-5555");
runner.setRace(race);
race.addRunner(runner);
em.persist(runner);
em.persist(race); //update the race now that it has a runner
|
リスト 12 では、Race インスタンスに Runner インスタンスを追加したことを反映するために、Race インスタンスを更新しなければならない点にも注意してください (さらにもう 1 つ注意する点として、Runner を単純に Runner の内部 Collection に追加する addRunner メソッドを追加しています)。
この場合も同じく、レースをその距離によって検索すると、レースに参加するランナーのリストも取得することができます (リスト 13 を参照)。
リスト 13. さらに面白い関係です!
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
List<Race> races = query.getResultList();
for(Race races : race){
System.out.println(race);
List<Runner> runners = race.getRunners();
for(Runner rnr : runners){
System.out.println(rnr);
}
}
|
EntityManager インスタンスを使用することによって、remove メソッドでエンティティーを削除できるようになります (リスト 14 を参照)。
リスト 14. クラス・インスタンスの削除
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
List<Race> races = query.getResultList();
for(Race races : race){
em.remove(race);
}
|
リスト 14 では Race インスタンスを削除していますが、関連する Runner は削除されません (もちろんこれは、JPA の EntityListeners アノテーションを使用して処理することができます。つまり削除イベントに接続し、そのイベントを使って Runner インスタンスを削除するということです)。
この SimpleDB の駆け足ツアーでは、非リレーショナル・データ・ストアのオブジェクトをAmazon Web サービス API と SimpleJP のそれぞれを使って操作する方法を説明しました。Simple JPA は、SimpleDB でオブジェクトの永続化を容易にするための Java Persistence API のサブセットを実装しています。SimpleJPA を使用すると便利なことは、この記事でおわかりのように、プリミティブ型を SimpleDB が認識するストリング・オブジェクトに自動的に変換してくれることです。SimpleJPA は関係をモデル化しやすくするために、SimpleDB の非結合のルールにも自動的に対処します。さらに、SimpleJPA の広範なリスナー・インターフェースにより、リレーショナルの世界ではおそらく当然と思われている論理データの整合性ルールを実装することも可能です。
要するに、SimpleJPA は、非常に高いスケーラビリティーを低コストで素早く、簡単に利用できるようにするための手段です。しかも SimpleJPA では、Hibernate のようなフレームワークで長年培ってきた知識を非リレーショナルのクラウド・ベースのストレージ環境で生かすことができます。
学ぶために
- 「Java 開発 2.0」: この developerWorks 連載では Java 開発全体を定義し直している技術とツールを探っています。これまで、Gaelyk (2009年12月)、Google App Engine (2009年8月)、CouchDB (2009年11月) などを取り上げました。
- 「NoSQL Patterns」(Ricky Ho 著、Pragmatic Programming Techniques、2009年11月): NoSQL データベースの概要と具体的な例を紹介した後、NoSQL データ・ストアの共通アーキテクチャーを詳細に検討しています。
- 「Amazon Web サービスを利用したクラウド・コンピューティング: 第 5 回 SimpleDB によるクラウド内でのデータセット処理」(Prabhakar Chaganti 著、developerWorks、2009年10月): Amazon SimpleDB の概念を学び、SimpleDB とのインターフェースに使われるオープンソースの Python ライブラリー boto に用意された関数について調べてください。
- 「Amazon Web サービスを利用したクラウド・コンピューティング: 第 1 回 概要」(Prabhakar Chaganti 著、developerWorks、2008年7月): スケーラブルで信頼性の高いアプリケーションを設計し、構築する上で Amazon Web Services がどのように強力な代替手段を提供するかを探っています。
- 「Google App Engine for Java: 第 3 回 永続化とリレーションシップ」(Richard Hightower 著、developerWorks、2009年8月): Rick Hightower が、Google App Engine の現状の Java 永続化フレームワークが抱える問題について説明し、その回避方法のいくつかを解説しています。
- Technology bookstore で、この記事で取り上げた技術やその他の技術に関する本を探してください。
- developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。
製品や技術を入手するために
- SimpleJPA: Amazon の SimpleDB のための JPA (Java Persistence API) 実装です。つまり、クラウド内の Amazon データベースのためのオブジェクト・リレーショナル・マッピング (ORM) フレームワークということです。
議論するために
- My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

Andrew Glover は、ビヘイビア駆動開発、継続的インテグレーション、アジャイル・ソフトウェア開発に情熱を持つ開発者であるとともに、著者、講演者、起業家でもあります。また、easyb BDD (Behavior-Driven Development) フレームワークの創始者、そして「継続的インテグレーション入門 開発プロセスを自動化する47の作法」、「Groovy in Action」、「Java Testing Patterns」の 3 冊の本の共著者でもあります。詳細は彼のブログにアクセスしてください。