Java標準のO/Rマッピング機能「Java Persistence API」
前回はEJB3.0のインターセプタ機能を使ったセッションBeanとコンテナ管理によるトランザクションについて説明しました。今回はJavaEE(Enterprise Edition)やJava SE(Standard Edition)におけるO/Rマッピングの標準になる「JavaPersistence API」*1を紹介しましょう。
Java PersistenceAPIを使うメリットは大きく二つあります。一つ目は,少ないプログラム・コードでデータにアクセスできることです。Java PersistenceAPIは,Javaオブジェクトをデータベースに格納したり,データベースのデータをJavaオブジェクトへ変換したりする処理を自動化してくれます。したがって,データベース・アクセス用のAPIであるJDBCを直接使ったアプリケーションよりも少ないプログラム・コードでデータベースへのアクセスを実現できます。
二つ目のメリットは,移植性の高いアプリケーションを作成できることです。通常,使用するデータベースが異なると,製品固有のSQLに依存したコードがネックになり移植が難しくなります。Java PersistenceAPIはデータベース製品間の違いの多くを吸収してくれるため,高い移植性を保てます。
Java PersistenceAPIを理解するには,まず主要な登場人物を覚えることが重要です。「エンティティ」「エンティティ・マネージャ」「永続コンテキスト」「EJBQL」の四つです。エンティティとはデータベースに永続化可能なJavaオブジェクトです。エンティティのクラスは通常のJavaクラスにアノテーション(注釈)や設定ファイルでエンティティであることを示して作成します*2。エンティティ・マネージャはJava PersistenceAPIにより提供されるインタフェースで,エンティティの取得や永続化を行うAPIを持ちます。永続コンテキストはメモリー上のエンティティの集合です。これはJava Persistence APIの実装によって管理されます。EJBQLはエンティティを取得するための問い合わせ言語です。記述方法はSQLに似ていますが,特定のデータベースに依存しません。
Java Persistence APIを利用したアプリケーションは図1のようになります。Java EEとJava SEのどちらでも基本的に構成は変わりません。
[size=-1]図1 Java Persistence APIを利用するアプリケーションのイメージ [画像のクリックで拡大表示] | |
Java Persistence APIに特有の考え方に注意しよう Java Persistence APIには多くの特徴があります。ただし,JDBC APIを直接使用する場合やJDBCAPIをベースにしたフレームワークを使用する場合にはあまり一般的でない考え方も含まれているので注意が必要です。Java PersistenceAPIの特徴の中で,特に注意すべきものとしては,次の三つが挙げられます。
(1)アノテーションを使ったJavaクラスとテーブルのマッピング Javaクラスはデータベースのテーブルに,Javaクラスのインスタンス変数はテーブルのカラムにそれぞれマッピングされます。マッピング情報の詳細はアノテーションで示すことができます。マッピングの仕方は様々です。複数のテーブルを一つのクラスにマッピングしたり,一つのテーブルを複数のクラスにマッピングしたりできます。最もシンプルなマッピングは,クラスとテーブルを一対一にすることです。この場合,エンティティ・クラスのインスタンス一つがテーブルのレコード1件に対応することになります。マッピングに関する代表的なアノテーションには,@Table,@Column,@Idなどがあります。
テーブルのマッピングだけでなくデータベース上のリレーションシップをアノテーションで示すこともできます。リレーションシップを表す代表的なアノテーションには,エンティティ間の多対1を表す@ManyToOne,1対多を表す@OneToManyなどがあります。データベースのリレーションシップをJavaのエンティティの定義に持ち込むことにより,Javaプログラムでオブジェクトのグラフ構造*3をたどってエンティティのデータにアクセスできるようになります。
JDBC APIを直接使っても,クラスとテーブルのマッピングをプログラム・コードで記述できますが,煩雑なコーディングが必要になり,生産性や保守性の低下につながります。
(2)エンティティの四つのライフサイクル エンティティには,四つの状態からなるライフサイクルがあります。「新規の(new)」「管理された(managed)」「分離された(detached)」「削除された(removed)」です。これらの状態によってエンティティに対して行える操作が限られるので注意してください。
Java PersistenceAPIでは,エンティティは概念的に「永続コンテキスト」という場で管理されます。上記の四つの状態は,永続コンテキストからみてどういう状態であるかを表しています。例えば「管理された」とは「エンティティが永続コンテキストで管理されている」状態を示します。「データベースに存在している」という意味ではありません。同様に,「削除された」とは「エンティティが永続コンテキストから削除された」という状態であり,データベースから削除されたという意味ではありません。
(3)トランザクションのコミット時に行われる同期化 データベースの永続化や更新や削除は,エンティティ・マネージャというJava PersistenceAPIが提供するオブジェクトが行います。一見,エンティティ・マネージャの更新系メソッドを実行すれば即座にINSERTやUPDATE,DELETEなどのSQLが発行されるように思えますが,そうではありません。エンティティの状態は,トランザクションのコミット時にデータベースに同期化されます*4。例えば,トランザクション内で同一のエンティティの値が2度以上変更されても,データベースに変更を反映するためのUPDATE文は1回しか実行する必要がないからです。このような仕組みを採用することで,データベースへのアクセスを極力減らすようになっています。
EJB 3.0/JPAの実行環境とサンプル・コードを用意する
ではサンプル・コードを動かしてエンティティやエンティティ・マネージャの使い方を確認してみましょう。
連載1回目と2回目のサンプルでは米JBossが開発したEJBの実装であるEmbeddable EJB 3.0のalpha 3というバージョンを使用しましたが,今回は原稿執筆時点(2006年2月下旬)で最新版のalpha 5 を使用します*5。最新のEJB3.0のドラフト仕様(Proposed Final)ではJava Persistence APIの部分を中心に変更が加えられましたが,alpha 5はその変更に対応しているからです。Embeddable EJB 3.0 alpha 5はJBossのサイトからダウンロードできます。ダウンロード後,以下の手順に従って環境を整えてください。データベースには前回と同様に,Embeddable EJB 3.0に含まれているHSQLDBを使用します。
(1)Eclipseのプロジェクト下に設定ファイルを配置します。使用するEmbeddable EJB 3.0のバージョンは異なりますが,設定方法は連載1回目と同じです。
・ダウンロードしたアーカイブから,conf,lib,docs\embedded-tutorial\simple-deployment\src\resources三つのフォルダをEclipseのプロジェクト直下にコピーする
・conf,resourceフォルダをソース・フォルダとし,libフォルダ内のjarファイルすべてにビルドパスを通す
・confフォルダ内にjndi.properties(リスト1)を作成する
(2)confフォルダ内のembedded-jboss-beans.xmlのHSQLDBのURLを変更します(リスト2)。
(3)サンプルで使用するEMPLOYEEテーブルをHSQLDBに作成します。EMPLOYEEテーブルを作成するためのSQL文(DDL)をリスト3に示しました。HSQLDBのサーバーやHSQLDB のDatabaseManagerの起動方法については連載2回目を参照してください。
(4)resource/META-INFフォルダ内のpersistence.xmlを開きHibernateに関する設定を変更します(リスト4)。
[size=-1]リスト1 必要な設定ファイルであるjndi.properties [クリックでリストをテキスト表示] | |
[size=-1]リスト2 embedded-jboss-beans.xmlの変更個所 [クリックでリストをテキスト表示] | |
[size=-1]リスト3 EMPLOYEEテーブルを作成するためのSQL文(DDL) [クリックでリストをテキスト表示] | |
[size=-1]リスト4 persistence.xmlの変更個所 [クリックでリストをテキスト表示] | |
persistence.xmlはJava PersistenceAPIで定められた設定ファイルです。persistence.xmlでは,エンティティ・マネージャの設定やエンティティ・クラスの指定,JavaPersistence APIの実装に対する設定などを行います。
リスト4では,Embeddable EJB 3.0がJava Persistence APIの実装として利用しているHibernateの設定を変更しています*6。JavaPersistenceAPIの実装に対する設定はproperties要素を利用して行います。HibernateにはエンティティからDDLを作成し発行する機能がありますが,今回は使用しないので無効にします。hibernate.hbm2ddl.autoプロパティにvalue="none"を指定してください。また,Hibernateには自動生成するSQLをログ出力する機能があります。Java PersistenceAPIの挙動を理解するために有効なので,今回はこれを利用します。hibernate.show_sqlプロパティにvalue="true"を設定してください。
次にサンプル・コードを作成します。サンプルではエンティティやエンティティ・マネージャの基本的な使い方を確認します。用意するのはEmployeeクラス(リスト5),エンティティ・マネージャを利用するEmployeeDaoBeanクラス(リスト6)とそのビジネス・インタフェース(リスト7),そしてmainメソッドをもつMainクラス(リスト8)です。これらのクラスとインタフェースをstep3というパッケージに作成してください。このサンプルが行うのは,Employeeエンティティの追加/更新/削除とEJB QLを使ったEmployeeエンティティの全件取得です。
サンプルを動かすにはHSQLDBのサーバーが稼働している環境でMainクラスを実行します*7。するとEclipseのコンソールに図2のような出力が表示され,Employeeエンティティの追加,更新,削除が行われていることを確認できます。図2では省略していますが,実際にはHibernateが生成するSQL文のログも表示されるはずです。EmployeeDaoBeanクラスで行っている処理とそれに対応するSQL文を見比べてみると,Java Persistence APIの挙動をイメージできます。
[size=-1]図2 サンプルを動作させたときにEclipseの
コンソールに表示される結果
(Hibernateが発行するSQLのログは省略) | | エンティティのアノテーションは指定する場所に注意 では,サンプルを詳しく説明しましょう。まず,エンティティの作成方法です。Employeeクラス(リスト5)はEMPLOYEEテーブルにマッピングされるエンティティ・クラスです。エンティティのクラスには@Entityというアノテーションを注釈する必要があります。そのほか次の規約に従う必要があります。
・引数なしのコンストラクタを持つこと
・クラスはfinalでなくトップレベルであること
・メソッドやフィールドがfinalではないこと
また,必須ではありませんが,エンティティが値渡しされる場合(リモート・インタフェースを介して利用される場合など),そのエンティティ・クラスはjava.io.Serializableインタフェースを実装する必要があります*8。今回のサンプルではそのような使い方をしないので,このインタフェースは実装していません。
[size=-1]リスト5 EMPLOYEEテーブルにマッピングされるエンティティ・クラスであるEmployeeクラス
[クリックでリストをテキスト表示] | | エンティティと同じ名前のテーブル名がある場合は,それらはデフォルトでマッピングされます。同様にインスタンス変数名とカラム名も,同じ名前ならマッピングされます。名称が異なるときは@Tableや@Columnといったアノテーションを使って任意の名称にマッピングすることができます。以下に@Tableや@Columnを使った例を示します。
@Entity
@Table(name="EMP")
public class Employee {
@Column(name="EMP_NAME")
private String name;
このコードでは,EmployeeクラスがEMPテーブルに,インスタンス変数nameがEMP_NAMEカラムにマッピングされます。
このほか,リスト5では「@Id」と「@GeneratedValue」というアノテーションも利用しています。@Idはプライマリ・キーに対応するインスタンス変数に指定します*9。@GeneratedValueはプライマリ・キーを生成する方法を指定するのに使用します。例えば,数値を自動生成する「シーケンス」というデータベースの仕組みを指定できます。この場合,@GeneratedValue(strategy=SEQUENCE,generator="CUST_SEQ")というように記述します。「strategy」でシーケンスを利用することを指定し,「generator」でデータベースのシーケンス・オブジェクトの名称を指定しています。サンプル・コードのように単に@GeneratedValueと記述した場合は,実行環境がデータベースに適切な生成方法を自動的に適用してくれます。プライマリ・キーを自動生成しないでプログラム内で明示的にセットする場合は,@GeneratedValueは不要です。リスト5では@GeneratedValueを指定しているので,データベースへのINSERT時にプライマリ・キーが自動で設定されます。
これらのアノテーションには注意すべき点があります。指定する場所です。リスト5の@Idや@GeneratedValue,先ほどの例で@Columnを注釈している個所に注目してください。これらのアノテーションはインスタンス変数に指定しています。一方,インスタンス変数に指定する代わりにgetterメソッドに指定することもできます*10。インスタンス変数に指定した場合とgetterメソッドに指定した場合の違いは,Java PersistenceAPIの実行環境(Hibernateなど)がどのようにインスタンス変数にアクセスするかの違いになります。インスタンス変数に指定した場合,実行環境はインスタンス変数に直接アクセスします。getterメソッドに指定した場合は,実行環境はgetterメソッドを介してインスタンス変数にアクセスします。後者の場合,getterメソッドで値を返す以外の処理をしていると,実行環境が値にアクセスするたびにその処理も実行されることになるので注意してください。
なお,インスタンス変数とgetterメソッドのどちらにアノテーションを指定したとしても,アプリケーションはメソッド経由でインスタンス変数にアクセスする必要があります。
エンティティ・マネージャでライフサイクルを制御 次にエンティティ・マネージャを利用してエンティティの取得や永続化を行うEmployeeDaoBeanクラス(リスト6)を見てください。エンティティ・マネージャは@PersistenceContextというアノテーションを使ってEmployeeDaoBeanにDI(Dependency Injection:依存性注入)できます。
[size=-1]リスト6 エンティティ・マネージャを利用するEmployeeDaoBeanクラス
[クリックでリストをテキスト表示] | | エンティティ・マネージャは,エンティティを永続化したり取得したりするメソッドを持ち,エンティティのライフサイクルを制御します。まず,エンティティのライフサイクルについて整理しましょう。エンティティのライフサイクルには四つの状態があります。
(1)新規の(new):
「new Employee( );」のようにプログラム上で新規に生成されたエンティティの状態です。プライマリ・キーを持たず,永続コンテキストにも関連付けられていません。
(2)管理された(managed):
プライマリ・キーを持ち,永続コンテキストに関連付けられた状態です。管理されたエンティティに対する変更は自動的に検出されトランザクションのコミット時にデータベースに反映されます。
(3)分離された(detached):
プライマリ・キーを持ち,永続コンテキストに関連付けられていない状態です。永続コンテキストに関連付けられたエンティティは永続コンテキストが終了するとすべて分離された状態になります。永続コンテキストはデフォルトではトランザクションが終わったときに終了します。サンプル・コードではEmployeeDaoBeanのメソッド呼び出しが完了するとトランザクションが終了します。したがって,EmployeeDaoBeanからMainクラスに返されるエンティティは,すべて分離された状態です。
(4)削除された(removed):
プライマリ・キーを持ち,永続コンテキストに関連付けられています。データベースからの削除が予定された状態です。実際の削除はトランザクションのコミット時に行われます。
以上の説明をふまえて,EmployeeDaoBeanで利用しているエンティティ・マネージャのメソッドについてそれぞれ説明します。
まず「createQueryメソッド」です。EJBQLを実行するのに必要なQueryインスタンスを作成します。EmployeeDaoBeanクラスのgetAllEmployeesメソッドで使われています。EmployeeDaoBeanでは「select e from Employeee」という文字列をcreateQueryメソッドに渡しています。これはEmployeeエンティティを全件返すEJBQLになります。「Employee」はテーブル名ではなくエンティティ名です。QueryインスタンスのgetResultListメソッドを実行することで,Employeeエンティティのリストを戻すことができます。EJB QLで返されるエンティティは「管理された」エンティティです。
EJB QLの文法は一般的なSQLによく似ています。条件や並べ替えの指定もSQLに似た形式で使用できます。例えば次のようなEJB QLを実行できます。
select e from Employee e where e.salary > 100000 order by e.name 次が「findメソッド」です。このメソッドはプライマリ・キーによる検索を行います。EmployeeDaoBeanクラスのupdateSalaryメソッドとremoveメソッドで使われています。第1引数で戻り値の型を指定できます。findメソッドで返されるエンティティは「管理された」エンティティです。
「mergeメソッド」は,「分離された」エンティティを永続コンテキストにマージして「管理された」エンティティを生成します。EmployeeDaoBeanクラスのupdateメソッドで使われています。「persistメソッド」は,「新規の」エンティティを永続化し,エンティティを「管理された」状態にします。EmployeeDaoBeanクラスのcreateメソッドで使われています。「removeメソッド」は,エンティティを削除しエンティティを「削除された」状態にします。EmployeeDaoBeanクラスのremoveメソッドで使われています。
ここで,「管理された」エンティティと「分離された」エンティティについて,もう少し詳しく見てみましょう。
EmployeeDaoBeanクラスのupdateSalaryメソッドを見てください。このメソッドでは,エンティティ・マネージャのfindメソッドにより「管理された」エンティティを取得し,salaryを変更しています。updateSalaryメソッドで行っているのはこれだけです。変更したエンティティをエンティティ・マネージャのメソッドに渡すといった処理はしていません。それにもかかわらず,このコードによりデータベースへの更新が行われます。なぜならば,「管理された」エンティティの変更は自動的に検出されるからです。
次にEmployeeDaoBeanクラスのcreateメソッドを見てください。このメソッドは,エンティティ・マネージャのpersistメソッドを使ってエンティティを「管理された」状態にしてから呼び出し元に返しています(この時点でデータベースへのINSERTが行われます)。しかし,呼び出し元のMainクラスがこのエンティティを受け取ったときには,エンティティはすでに「分離された」状態になっていることに注意してください。トランザクションと同時に永続コンテキストが終了しているためです。Mainクラスは「分離された」エンティティに対して変更を行いますが,これだけではデータベースへ変更は伝わりません。この変更をデータベースへ反映させるには,エンティティ・マネージャのmergeメソッドを利用する必要があります。EmployeeDaoBeanクラスのupdateメソッドを見てください。EmployeeDaoBeanクラスのupdateメソッドでは「分離された」エンティティをエンティティ・マネージャのmergeメソッドに渡し,新しく「管理された」エンティティを生成しています。サンプル・コードではmergeメソッドが「管理された」エンティティを返すことをわかりやすく示すために
Employee managed =
entityManager.merge(employee);
としています*11。
EmployeeDaoBeanクラスのupdateメソッドでは,トランザクションのコミット時に「管理された」エンティティとデータベースの実際のデータが比較され,自動的に適切な更新が行われます。
エンティティのライフサイクルとエンティティ・マネージャのメソッドの関係を図3にまとめました。
[size=-1]図3 エンティティのライフサイクル | |
[size=-1]リスト7 ビジネス・インタフェースであるEmployeeDaoインタフェース
[クリックでリストをテキスト表示] | |
[size=-1]リスト8 mainメソッドを持つMainクラス [クリックでリストをテキスト表示] | |
☆ ☆ ☆ さて,今回でEJB 3.0入門講座はおしまいです。限られたスペースでしたが,EJB 3.0について基本となるところを中心に取り上げました*12。EJB 3.0は,まだ最終的な仕様がリリースされていない新しい技術ですが,実際の開発でも有効です。このことを感じ取っていただけたでしょうか。この連載が皆さんにとってEJB 3.0に触れるきっかけになれば幸いです。 |