目次


Java 開発 2.0

Amazon の SimpleDB によるクラウド・ストレージ、第 2 回

SimpleJPA による昔ながらの簡素なオブジェクト・パーシスタンス

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Java 開発 2.0

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Java 開発 2.0

このシリーズの続きに乞うご期待。

SimpleDB を紹介するこの連載の前半では、Amazon 独自の API を使用して CRUD スタイルのレース参加アプリケーションをモデル化する方法を説明しました。ほとんどの Java 開発者にとって、データ型にストリングだけを使うという Amazon の手法は明らかに特異なものですが、それは別としても、Amazon API に懐疑的な思いを抱いた方はいることでしょう。しかし結局のところ、リレーショナル・データベースを利用するための API は今ではかなり標準的で十分に検討された API になっており、もっと重要なことは皆さんが使い慣れてきていることです。

最近のリレーショナル・フレームワークの多くには、Java Persistence API が実装されているため、ほとんどのタイプの Java アプリケーションでは、RDBMS でドメイン・オブジェクトをモデル化するのが簡単で、お馴染みの作業となっています。ドメインを上手くモデル化する方法をすでにマスターしているというのに、さらに新しい方法を学ぶのには抵抗を感じて当然です。幸い、SimpleDB では新しい方法を学ぶ必要はありません。

この SimpleDB を紹介する連載の後半では、第 1 回のレース参加アプリケーションをリファクタリングして JPA 仕様に準拠させる方法を説明します。その後、このアプリケーションを SimpleJPA に移植して、この革新的なオープンソースのプラットフォームが NoSQL のドメイン・モデリングへの順応、そしてクラウド・ベースのストレージの利用を多少なりとも容易にするいくつかの方法に目を向けます。

Hibernate と JPA: その略歴

最近の 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 の関係は論理的にモデル化されています。現実の世界では、多対多のリンクを作ったほうが適切だと思いますが (普通、ランナーは複数のレースに参加するものです)、ここでは複雑にならないよう、このままにしておきます。また、今のところはコンストラクター、セッター、ゲッターを省略してありますが、これらについては後で説明します。

JPA でのアノテーション

この 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 の管理が行われることを前提にします。

SimpleJPA の登場

これまでのところ、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 を生成します)。

SimpleJPA へのアプリケーションの移植

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 に準拠させるための最初のステップは、RaceRunner の ID を更新することです。次に、オブジェクトのプリミティブ・データ型 (doubleintfloat など) を IntegerBigDecimal などのオブジェクトに変える必要があります。

まずは Racedistance プロパティーから取り掛かることにします。私の経験から、BigDecimal は (現行の SimpleJP リリースでは) Double よりも信頼できると思うので、Racedistance プロパティーは 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...
}

これで、RunnerRace の両方を SimpleJPA 実装で永続化する準備が整いました。

SimpleDB で 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'

自動パディング処理とエンコードによって、大幅に手間が省けると思いませんか?

SimpleJPA での関係

SimpleDB ではクエリーでのドメイン結合を許可していないとは言え、複数のドメインで関連アイテムを使用することは可能です。第 1 回で説明したように、関連オブジェクトのキーを別のオブジェクトに保管するだけで、そのオブジェクトを必要なときにいつでも取得できるようになります。SimpleJPA でも同じことを行います。例えば、前に JPA アノテーションを使って複数の RunnerRace にリンクする方法を紹介しました。つまり、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 のようなフレームワークで長年培ってきた知識を非リレーショナルのクラウド・ベースのストレージ環境で生かすことができます。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, Cloud computing
ArticleID=516909
ArticleTitle=Java 開発 2.0: Amazon の SimpleDB によるクラウド・ストレージ、第 2 回
publish-date=08032010