本連載は、サンプル・アプリケーションの開発を通じてJava Platform, Enterprise Edition 6 (Java EE 6)の仕様と、その魅力をお伝えすることを目的としています。今回から2回にわたって、Java Persistence API(JPA)をとりあげます。JPAは簡単に言うと、O/Rマッピングのフレームワークで、Javaのオブジェクトとリレーショナルデータベースのデータを相互にマッピングします。JPAを利用することにより、開発者はJavaのオブジェクトを操作し、簡単にデータベースのデータを参照、更新することが可能になります。 |
JPAの特長
JPAを説明する前に、このようなO/Rマッピングフレームワークが登場した歴史を説明しなければなりません。これまでのデータベースと連携したJavaのシステム開発は、JDBCプログラミングを通して行われました。JDBCプログラミングは一般的に以下のようなデータベースにアクセスするための煩雑なコードを記述する必要がありました。
- データソースクラスを定義
- データベースの物理コネクションを取得
- トランザクションの開始
- SQLクエリーの作成とパラメータの設定
- SQLの実行とその結果をJavaのオブジェクトに変換
(ResultSetオブジェクトから、任意のJavaBeanへのデータの詰め替え) - トランザクションの終了(コミットやロールバック)
- コネクションのクローズ
- 上記の一連処理のエラーハンドリング etc
上記の、SQLの結果をJavaのオブジェクトに変換する作業は、とても煩雑な作業です。なぜなら、Javaのオブジェクトとリレーショナルデータベースは、データモデルの考え方が異なるからです。このデータモデルの考え方の違いをインピーダンスミスマッチと呼びます。O/Rマッピングフレームワークは、このインピーダンスミスマッチを自動で解決し、異種のデータを相互変換します。
Hibernate等の各種O/Rマッピングフレームワークが、実際の開発現場で多く採用されました。
そして2006年にJPAは、このO/Rマッピングの標準仕様として策定されました。
JPAを使用する利点は、以下の通りです。
- O/Rマッピングや、データベースアクセス処理の隠ぺいによる、ソースコード記述量の削減
- アノテーションと、複雑な定義ファイル記述量の大幅削減によるかんたん開発の実現
- データベースベンダーの特殊性に依存しにくい、移植性の高いアプリケーションの実現
JPA2.0新機能
Java EE 6に含まれる最新のJPAのバージョンは2.0であり、以下の新機能が追加されました。
- 楽観的ロックに加え悲観的ロックの追加
MyEntity men = new MyEntity(); em.persist(men); // エンティティの悲観的ロック em.lock(men,LockModeType.PESSIMISTIC_WRITE)
- 検索結果のソートとソート結果の保持
- DBレコードの連鎖削除(カスケード削除)
@Entity public class Order { // カスケード削除の指定 // meisaiList削除時にリスト内のEntityに対応するレコードもDBから削除 @OneToMany(Cascade=PERSIST, orphanRemovable=true) private List
meisaiList; //... } - JPQL演算子の追加(INDEX / CASE / TYPE、etc.)
- 2次キャッシュの標準仕様化
- コレクションサポートの拡充
- 基本型のコレクションのテーブルへのマッピング
- 組み込み可能型コレクションのマッピング
- 多段にネストされた組み込み型もマッピング可能
- Map型コレクションの柔軟性向上
- キー、値に基本型、組み込み可能型、Entity型を利用可能
- Criteria APIによるクエリーの記述
JPQLに相当するクエリーをAPIベースで記述し型の安全性を確保
JPA2.1
また、現在策定中のJPA2.1はJava EE 7に含まれる予定です。ストアドプロシージャのサポートなど、いくつかの機能が追加される予定です。
Oracle Database Express Edition 11g Release 2の準備
作成するアプリケーションは、Oracle Database Express Edition 11g Release 2(以下XE11g)を使用します。事前にダウンロード、インストールを行い、HRユーザのロックを解除してください。
XE11gは、次のページ
http://www.oracle.com/technetwork/jp/database/express-edition/overview/index.htmlよりダウンロードして下さい。
インストール後、デフォルトで定義されているHRユーザのロックを解除し、HRユーザのパスワードに”hr”を設定します。下記の手順で実施してください。
- SQLコマンド・プロンプト・ウィンドウを表示します。たとえば、Windowsでは、「スタート」、「プログラム」(または「すべてのプログラム」)、「Oracle Database 11g Express Edition」、「SQLコマンドラインの実行」の順にクリックします。
- SYSTEMユーザとして接続します。
- 入力: connect
- ユーザ名の入力: system
- パスワードの入力: (password-for-system)
- 次の文を入力し、HRアカウントをロック解除します。
SQL> ALTER USER hr ACCOUNT UNLOCK;
- 次の形式で文を入力し、HRユーザのパスワードを指定します。
SQL> ALTER USER hr IDENTIFIED BY hr;
- 必要に応じて、SQL*Plusを終了します(これによりコマンド・ウィンドウも閉じられます)。
SQL> exit
WebLogic Server のデータソースの作成
本連載では、 Oracle Enterprise Pack for Eclipse (OEPE)を使用してサンプル・アプリケーションの開発を進めます。 OEPE や WebLogic Server 12c のインストールおよび設定が終わっていない方は、第1回の記事「[連載] WebLogic Server 12cでJava EE 6 を動かしてみよう!(1) 概要」を参考にしてください。
WebLogic Server上にデータベース接続のためのデータソースを作成します。事前に、インストールしたXE11gを起動しておいてください。
第1回の記事で作成したtest1ドメインの管理サーバを起動します。バッチファイルで起動する場合は、下記の手順で管理サーバを起動します。
#> cd D:\wls12c\user_projects\domains\test1\bin #> startWebLogic.cmd
Webブラウザにてhttp://localhost:7001/console にアクセスし、WebLogic Serverの管理コンソールにアクセスします。
test1ドメインのユーザ名、パスワードを入力して、管理コンソールにログインします。
左ペインより、「サービス」―「データソース」を選択します。JDBCデータソースのサマリーから「新規」―「汎用データソース」を選択します。
JNDI名に「xe11g」と入力します。JNDI名は、後ほどアプリケーションがデータベースに接続する際に必要な値となります。「次」ボタンをクリックします。
デフォルトのまま、「次」ボタンをクリックします。
デフォルトのまま「次」ボタンをクリックします。
接続プロパティに、データベースへの接続設定を下記の通り入力します。ホスト名、ポートに関しては、XE11gをローカルマシンにポートをデフォルトの設定でインストールした際の設定値となります。
- データベース名:「xe」
- ホスト名:「localhost」
- ポート:「1521」
- データベース・ユーザ:「HR」
- パスワード:「hr」
- パスワードの確認:「hr」
上記の入力が完了したら「次」ボタンをクリックします。
「構成のテスト」ボタンをクリックし、正常にデータベースと接続可能か否かを確認します。
下記の画面のように成功した旨メッセージが表示されます。
接続に失敗した場合は、下記画面のエラーメッセージが表示されますので、再度データベースの起動の成否、及び、データソースの設定を確認の上、「構成のテスト」ボタンを再度クリックします。
ターゲットの選択として、「AdminServer」にチェックをいれ、「終了」ボタンを押下します。
以上で、データソースの設定が完了しました。WebLogic Serverの再起動は不要です。
作成するアプリケーション
アプリケーションは、Java EE 6 の仕様の一部である、JSFやEJBも一緒に使ってJava EE環境で構築します。なおJPAは、Java EE環境だけでなく、Java SE環境でも利用することができます。
JSFで作成した画面から、EMPLOYEES表に対する、参照、挿入、更新、削除、リフレッシュ(データベースとの同期)を行います。今回は、XE11gで定義済みのテーブルを使用したアプリケーションを作成します。
アプリケーションの作成手順は以下の通りです。
- OEPEのプロジェクトの作成
OEPEで動的Webプロジェクトを作成し、JPAファセットを追加します。 - エンティティの作成
XE11gより、既存テーブルからエンティティを自動生成します。 - persistence.xmlの定義
WebLogic Serverで作成したデータソースを使用するように設定します。 - ビジネスロジックの作成
セッションBeanを作成し、データベース操作を行うビジネスロジックを記述します。 - 画面の作成
JSFを使用した画面の作成を行います。
エンティティの作成
JPAの概念には、エンティティというクラスが存在します。エンティティとは、データベースのテーブルにマッピングするJavaクラスのことです。このエンティティを操作することにより、データベースのレコードを挿入、参照、更新、削除することができます。まずは、OEPEを使用し、データベースの1つのテーブルに対して、シンプルなエンティティを作成しましょう。
エンティティは、POJOに対して、@Entityアノテーションを付与するだけで簡単に作成することができます。今回はJavaのエディタでコーディングするのではなく、データベースのテーブル構造を参照し、エンティティのソースコードを自動生成します。
プロジェクトの作成
「New」―「Dynamic Web Project」を選択します。Project nameに「JPA」と入力、Target runtimeは「Oracle WebLogic Server 12c (12.1.1)」を選択し、「Finish」ボタンをクリックします。
以上で、プロジェクトの作成は完了です。
エンティティの作成
エンティティを作成するには、下記の手順でプロジェクトにJPAのファセットを追加します。
- JPAプロジェクトを選択、右クリックでポップアップメニューを表示し、「Properties」を選択します。
- 左ペインより「Project facets」を選択し、「JPA」のチェックボックスにチェックをいれます。
- 「OK」ボタンをクリックします。
「JPA」プロジェクトを選択し、右クリック「JPA Tools」―「Generate Entities from Tables…」を選択します。
「Connection」はJDBCデータソース作成時の設定を参考に新規作成してください。
XE11gとの接続設定を行い、Schemaに「HR」を選択し、Tablesから「EMPLOYEES」にチェックをいれます。「Next」ボタンをクリックします。
Table Associations画面では、デフォルトのまま「Next」ボタンをクリックします。
本画面では、テーブル間の関連に関する設定を作成、編集することができます。
Packageに「model」を入力し、「Finish」ボタンをクリックします。
以下の、EMPLOYEES表のエンティティが自動生成されます。自動生成された下記エンティティのJavaファイルをダブルクリックして、ファイルの内容を確認しましょう。
package model; import java.io.Serializable; import javax.persistence.*; import java.math.BigDecimal; import java.util.Date; import java.util.Set; @Entity @Table(name="EMPLOYEES") public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name="EMPLOYEE_ID") private long employeeId; @Column(name="COMMISSION_PCT") private BigDecimal commissionPct; @Column(name="DEPARTMENT_ID") private BigDecimal departmentId; private String email; @Column(name="FIRST_NAME") private String firstName; @Temporal( TemporalType.DATE) @Column(name="HIRE_DATE") private Date hireDate; @Column(name="JOB_ID") private String jobId; @Column(name="LAST_NAME") private String lastName; @Column(name="PHONE_NUMBER") private String phoneNumber; private BigDecimal salary; //bi-directional many-to-one association to Employee @ManyToOne @JoinColumn(name="MANAGER_ID") private Employee employee; //bi-directional many-to-one association to Employee @OneToMany(mappedBy="employee") private Setemployees; /* コンストラクタ、setter/getterは省略 */ }
エンティティは、各種アノテーション等が付与されていること以外は、普通のJavaのオブジェクトであることが分かります。
@Entityアノテーションが、クラスに付与されています。これにより、WebLogic Server(正確に言うとJPAプロバイダ)は、本クラスEmployeeが、データベースのテーブルとマッピング対象のエンティティであることを認識します。今回は、1つのテーブルと1つのクラスを1対1でマッピングしましたが、多対1でマッピングさせることも可能です。
また、@IdアノテーションをemployeeIdに付与し、本属性が主キーであることを示します。@Tableアノテーションでマッピング対象のテーブル名を定義します。@Tableアノテーションを付与しない場合は、クラス名と同一の名称のテーブルをマッピング対象とします。
@Temporalアノテーションは、時刻を表す型を、どのデータベースの型に対応させるかを指定します。上記の場合は、java.util.Date型のメンバー変数をTemporalType.DATE(java.sql.Data型)と対応させるように指定しています。
@ManyToOne、@OneToManyアノテーションは、エンティティの関連を定義します。関連の方向性(一方向、もしくは双方向)や多重度を定義することができます。本記事では、詳細な説明は割愛いたします。
以上で、エンティティの作成は完了しました。しかし、これだけではテーブルとマッピング対象の入れ物を作成したに過ぎません。このエンティティを用いて、データベースのレコードを参照、更新するには、エンティティマネージャを使います。
persistence.xmlの定義
JPAを使用するには、エンティティの他に、設定ファイルの記述が必要です。JPAの設定ファイルは、persistence.xmlというXML形式の設定ファイルです。OEPEでJPAファセットをプロジェクトに追加した際に自動生成されます。以下のファイルをダブルクリックで開き、修正します。
- Java Resources/src/META-INF/persistence.xml
persistence.xml専用のエディタが表示されます。エディタの「Connection」タブをクリックし、下記の設定を行います。
- Transaction type : 「Default(JTA)」
- JTA data source : 「xe11g」
上記設定値の入力が終わったら、persistence.xmlを上書き保存してください。JTA data sourceで入力した値は、WebLogic Server上で設定したJDBCデータソースのJNDI名となります。
エンティティのライフサイクル
エンティティのライフサイクルに関して説明します。エンティティは、new演算子によりインスタンス化することができますが、インスタンス化しただけでは、データベースに何も反映されません。エンティティは管理状態となって、初めてデータベースと連携し、テーブルに対象レコードを挿入します。管理状態のエンティティに対して、属性値を更新、またはエンティティ自体を削除し、データベースに反映することができます。
トランザクションの完了等により、エンティティは管理状態から外れ、デタッチ状態に遷移します。デタッチ状態のエンティティは、セッタメソッド等で属性値を変更しても、データベースには反映されません。反映するためには、デタッチ状態のエンティティを再度管理状態にする必要があります。
上記の操作は、エンティティマネージャが提供するメソッドを使用して実現します。
エンティティ操作のためのビジネスロジックの作成
エンティティ操作のためのビジネスロジックを作成します。今回は、EJBのセッションBeanを作成し、その中で、エンティティマネージャを使用してエンティティの操作を行います。
エンティティマネージャは、エンティティを操作するためのAPIを提供します。これらAPIが提供するものは、参照(主キー検索)のためのfindメソッドや、挿入のためのpersistメソッド等です。
さっそくEmpLogic.javaを作成します。下記ファイルを作成してください
package ejb; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import model.Employee; @Stateless public class EmpLogic { @PersistenceContext(unitName = "JPA") private EntityManager em; public Employee getEmp(long id) { return em.find(Employee.class, id); } public Employee getEmpWithRefresh(long id) { Employee emp = em.find(Employee.class, id); em.refresh(emp); return emp; } public void createEmp(Employee emp) { em.persist(emp); } public void updateEmp(Employee emp) { em.merge(emp); } public void removeEmp(Employee emp) { Employee managedEmp = em.merge(emp); em.remove(managedEmp); } }
メンバー変数のEntityManager型に@PersistenceContextアノテーションが付与されています。これにより、エンティティマネージャの参照を自動的に注入します。@PersistenceContext(unitName = "JPA")は、persistence.xmlの<persistence-unit name="JPA">に関連付けられます。
レコードの参照
レコードの参照は、EntitiyManagerのfindメソッドを呼び出します。findメソッドの引数には、取得対象のエンティティのクラス情報と、主キーを指定します。
public Employee getEmp(long id) { return em.find(Employee.class, id); }また、refreshメソッドを呼ぶと、データベースのデータでエンティティを上書きすることができます。
public Employee getEmpWithRefresh(long id) { Employee emp = em.find(Employee.class, id); em.refresh(emp); // データベースから再度レコードを取得する return emp; }レコードの取得に成功すると、エンティティのインスタンスを取得します。
レコードの挿入
レコードの挿入は、EntitiyManagerのpersistメソッドに、挿入対象のエンティティのオブジェクトを指定します。挿入対象のエンティティをnewすることにより、新しいインスタンスができますが、このインスタンスは、JPAの管理対象ではありません。よって、前述の通りpersistメソッドを用いて管理対象としない限りは、データベースには反映されません。
管理対象となったエンティティは、後で(コミット等のタイミングで)JPAプロバイダがデータベースに挿入(INSERT)します。
// 引数にインスタンス化(new)したエンティティを受け取る public void createEmp(Employee emp) { em.persist(emp); }
エンティティマネージャには2種類あり、コンテナ管理のエンティティマネージャと、アプリケーション管理のエンティティマネージャがあります。
コンテナ管理のエンティティマネージャは、上記EmpLogic.javaの通り、EntityManagerインスタンスをDIで注入して使用できます。また、コンテナがトランザクション管理を行うため、トランザクション管理のためのコードを記述する必要がありません。
一方で、アプリケーション管理のエンティティマネージャは、Factoryクラスを使用して、EntityManagerインスタンスを取得します。
Java SE環境でJPAを使用する場合は、アプリケーション管理のエンティティマネージャを使用しますので、トランザクション制御をプログラマが行う必要があります。よって、更新系の処理を行う場合は、下記サンプルコードのようにトランザクションの開始、コミット、ロールバックを記述しなくてはいけません。
EntityManagerFactory emf = Persistence.createEntityManagerFactory("DB"); EntityManager em = emf.createEntityManager();// エンティティマネージャ生成 EntityTransaction tx = em.getTransaction(); tx.begin(); // トランザクションの開始 em.persist(customer); tx.commit(); // トランザクションのコミット em.close(); // エンティティマネージャのクローズ emf.close();
レコードの更新
レコードの更新は、EntitiyManagerのmergeメソッドにエンティティのオブジェクトを指定します。mergeメソッドを呼び出すことで、エンティティが、永続化コンテキストの管理対象となったため、データベースに対して更新処理が行われます。
// 引数にデタッチ状態のエンティティを受け取る public void updateEmp(Employee emp) { em.merge(emp); }永続化コンテキストの管理対象になっていない場合、仮にエンティティの属性値を変更しても、データベースに変更は反映されません(UPDATEクエリーは発行されません)。前述の通り、管理対象となっていない状態のことをデタッチ状態と呼びます。
レコードの削除
レコードの削除は、EntitiyManagerのremoveメソッドに削除対象のエンティティのオブジェクトを指定します。public void removeEmp(Employee emp) { // 削除を行うためにエンティティを管理対象にする Employee managedEmp = em.merge(emp); // 削除を行う em.remove(managedEmp); }
引数のエンティティは、管理対象ではないので、mergeメソッドを呼び管理対象とし、removeメソッドを呼びます。管理対象ではないエンティティを削除しようとすると、実行時エラーが発生します。
これで、トランザクションがコミットされたタイミングで、データベースにもエンティティの削除が反映されます。removeメソッドを呼び出した後は、データベースから削除対象のレコードとなりますが、エンティティのインスタンス自体は、ガベージコレクションで回収されるまでは、プログラムで使用することができます。
以上のように、Java EE環境でJPAを使用した場合、参照系の場合であれば、SELECT文を記述することなく、またResultSetインスタンスから、任意のJavaオブジェクトに値の詰め替え作業を行わなくても参照処理の実装ができました。データベース接続のためのコネクションの取得、クローズ処理もプログラマの記述が不要です。
また、更新系の処理も同様に、INSERT、UPDATE、DELETE文の記述を行わなくても、APIの呼び出しだけで、更新処理の実装が可能です。トランザクション管理も、Java EEコンテナが制御してくれますので、これも記述の必要がありません。
前述の通り、Java SE環境でJPAの実装を行う場合は、persistence.xmlのロードや、トランザクション管理はプログラマの仕事となります。
次項で作成したエンティティやセッションBeanの動作を確認するための簡単な画面作成を行います。
画面の作成(JSF)
JSFに関しては、前回の記事でご紹介しましたので、詳細な解説を省略します。詳細は「[連載] WebLogic Server 12cでJava EE 6を動かしてみよう!(2) JSF 第1回」をご確認ください。下記の通り、1つのJavaファイルと1つのXHTMLファイルを作成してください。JPAプロジェクトに、JSFのファセットを追加すると便利です。
- Java Resources/src/managed/IndexBean.java
- WebContent/index.xhtml
XHTMLファイルの作成
JPAプロジェクトの「WebContent」ディレクトリ配下の「index.xhtml」を下記の通り編集します。<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"><h:head><title>JPA</title></h:head><h:body><h:form><h:panelGrid columns="2"><h:outputText value="ID" /><h:inputText id="empid" value="#{indexBean.dispEmp.employeeId}" required="true" /><h:outputText value="FIRST NAME" /><h:inputText value="#{indexBean.dispEmp.firstName}" /><h:outputText value="LAST NAME" /><h:inputText value="#{indexBean.dispEmp.lastName}" /><h:outputText value="EMAIL" /><h:inputText value="#{indexBean.dispEmp.email}" /><h:outputText value="HIRE DATE" /><h:inputText value="#{indexBean.dispEmp.hireDate}"><f:convertDateTime pattern="yy-MM-dd" /></h:inputText><h:outputText value="JOB ID" /><h:inputText value="#{indexBean.dispEmp.jobId}" /></h:panelGrid><h:message for="empid" /><br /><h:commandButton value="GET" action="#{indexBean.actionGetEmp}" /><h:commandButton value="GET(REFRESH)" action="#{indexBean.actionGetEmpRefresh}" /><br /><h:commandButton value="CREATE" action="#{indexBean.actionCreateEmp}" /><h:commandButton value="UPDATE" action="#{indexBean.actionUpdateEmp}" /><h:commandButton value="REMOVE" action="#{indexBean.actionRemoveEmp}" /><h:commandButton value="CLEAR" action="#{indexBean.actionClearEmp}" /></h:form></h:body></html>
マネージドBeanの作成
「Java Resources/src/managed」ディレクトリ配下に、「IndexBean.java」を作成します。package managed; import javax.ejb.EJB; import javax.faces.bean.ManagedBean; import model.Employee; import ejb.EmpLogic; @ManagedBean public class IndexBean { // @EJBアノテーションを付与し // 作成したEmpLogicをマネージドBeanに注入します。 @EJB private EmpLogic empLogic; private Employee dispEmp = new Employee(); public Employee getDispEmp() { return dispEmp; } public void setDispEmp(Employee dispEmp) { this.dispEmp = dispEmp; } public void actionGetEmp() { dispEmp = empLogic.getEmp(dispEmp.getEmployeeId()); } public void actionGetEmpRefresh() { dispEmp = empLogic.getEmpWithRefresh(dispEmp.getEmployeeId()); } public void actionCreateEmp() { empLogic.createEmp(this.dispEmp); } public void actionUpdateEmp() { empLogic.updateEmp(this.dispEmp); } public void actionRemoveEmp() { empLogic.removeEmp(dispEmp); actionClearEmp(); } public void actionClearEmp() { dispEmp = new Employee(); } }
実行確認
WebContent/index.xhtmlを選択し、右クリック「Run as」―「Run on Server」を選択します。下記の画面がブラウザ上に表示されます IDに「101」を入力し、「GET」ボタンをクリックします。IDが101のEmployeeデータが表示されます。「GET(REFRESH)」ボタンは、データベースのデータと同期を行います。Employee表が直接書き換えられた場合に、その更新をエンティティにも反映します。
「CREATE」ボタンは、入力値を元に新規レコードをデータベースに挿入します。「UPDATE」、「REMOVE」ボタンは、表示中のレコードに対して、更新、削除を行います。
まとめ
今回は、JPAの基本をJava EEのアプリケーションの作成を通して、ご理解いただけたかと思います。次回は、JPQLやCriteriaAPI等を使ったクエリー文や、応用的なJPAの使い方を紹介します。