Java Persistence を利用する Web 専用アプリケーションのデザイン選択肢

ステータス:Early Access
Sean BrydonSmitha Kangath

課題

新しい Java Persistence API には、Web アプリケーションのモデル層の新しいデザイン方法があります。Java Persistence を含む Java EE 5 の仕様書は長編で、多くの技術をカバーしています。Java Persistence API を利用する Web アプリケーションのデザインには多数の選択肢があるため、このドキュメントでは、主要なプログラミングモデルの選択肢の一部を簡単に取り上げます。 エンティティーマネージャー、エンティティー、トランザクションの使用、および注釈の使用にあたっての考慮事項の概要を示します。

まず、ここでの「Web 専用 (Web-only)」アプリケーションの意味するものを定義して、見てみましょう。Web 専用アプリケーションとは、WAR ファイルとしてパッケージ化され (EAR ファイルにパッケージ化されることもある)、Java EE 5 プラットフォームが対応しているアプリケーションサーバーに配備する、EJB モジュールを含まないアプリケーションを意味します。 このドキュメントでは、セッション Bean などの EJB がエンティティーのクライアントの働きをするケースは取り上げません。 Web 専用アプリケーションは、Java Persistence API を利用したサーブレットなどの Web コンポーネントで構成されます。JNDI ネーミングサービスやセキュリティーサービスなどの Java EE コンテナのあらゆるサービスを利用できるようにします。これは、Java EE 5 が対応している任意のアプリケーションサーバー上で動作可能な、移植性のある Web アプリケーションです。その他のアプリケーションアーキテクチャー、たとえば、Java Persistence と EJB を組み合わせた Web アプリケーションや Web コンテナに配備する Web アプリケーションについては、その他のドキュメントで取り上げます。また、Java SE 環境における Java Persistence (Java Persistence はJava EE コンテナなしで動作可能) についても、その他のドキュメントで取り上げます。 Web 専用アプリケーションのプログラミングモデルは、EJB モジュールを持つ Web アプリケーションとは異なります。このドキュメントで概説する選択肢や制約事項は、Web 専用アプリケーションのプログラミングモデルにのみ関係します。

Java Persistence API を利用して Web アプリケーションをデザインする場合は、次の事項を考慮してください。
これらの選択も含め、ほかの多くの項目がアプリケーションのデザインに影響します。これらをさらに詳しく検討し、Web アプリケーションに Java Persistence を使用する場合のプログラミングモデルを明らかにしてみましょう。

対処法

持続性コンテキスト内では、エンティティーインスタンスとそのライフサイクルは、エンティティーマネージャーによって管理されています。エンティティーマネージャーには、コンテナ管理とアプリケーション管理の 2 つの種類があります。Web 専用アプリケーションでの主なプログラミングモデルの選択では、コンテナ管理エンティティーマネージャーを使用するか、またはアプリケーション管理エンティティーマネージャーを使用するかを選択します。なぜこの選択が重要なのでしょうか。1 つには、アプリケーション管理の場合、アプリケーションがプログラム的にエンティティーマネージャーのライフサイクルを管理する必要があるため、アプリケーションのロジックのコーティング量が多くなります。コンテナ管理の場合、エンティティーマネージャーのライフサイクルは Java EE コンテナによってトランスペアレントに管理されます。しかし、この選択は、各モデルに制約事項と長短がある点でも重要です。 表 1 は両方の選択肢の特徴を簡単に比較したものです。
 
アプリケーション管理エンティティーマネージャー コンテナ管理エンティティーマネージャー
エンティティーマネージャーインスタンスの取得は、エンティティーマネージャーファクトリを使って指定 エンティティーマネージャーインスタンスの取得は、@PersistenceContext 注釈または JNDI ルックアップを使って指定
拡張スコープの持続性コンテキストが必須 トランザクションスコープの持続性コンテキストが必須
アプリケーション管理トランザクションが必須 アプリケーション管理トランザクションが必須
表 1: アプリケーション管理エンティティーマネージャーとコンテナ管理エンティティーマネージャーの特徴

コンテナ管理またはアプリケーション管理エンティティーマネージャーのどちらにするかは、アプリケーションコード内で指定します。アプリケーションコードが EntityManager インスタンスの取得に JNDI ルックアップまたは @PersistenceContext による依存性注入を使用している場合、コンテナ管理エンティティーマネージャーになります。 アプリケーションコードがエンティティーマネージャーの取得に EntityManagerFactory.createEntityManager メソッドを使用している場合、アプリケーション管理エンティティーマネージャーになります。

コンテナ管理またはアプリケーション管理エンティティーマネージャーを選択することから生じる 1 つの影響は、その選択によって、持続性コンテキストのライフタイムの種類がトランザクションスコープか拡張スコープに制約されることです。持続性コンテキストはトランザクションスコープまたは複数のトランザクションにまたがる拡張スコープのいずれかにできます。 Web 専用アプリケーションの場合、アプリケーション管理エンティティーマネージャーは拡張スコープになり、コンテナ管理エンティティーマネージャーはトランザクションスコープになります。ここでのデザイン上の課題は、持続性コンテキストのライフタイムを単一トランザクションのスコープにするか、複数のトランザクションにまたがるようにするかです。トランザクションスコープの持続性コンテキストは、現在の JTA トランザクションの間だけ存在します。拡張スコープの持続性コンテキストは、トランザクション境界を越えて存続します。これは、セッションのように、エンティティーマネージャーを複数の HTTP 要求間でアクティブにしておくことが望ましくないケースで使用されます。拡張スコープのトランザクションを使用する必要がある場合、アプリケーションはアプリケーション管理エンティティーマネージャーを使用する必要があります。EJB を使用した Web アプリケーションの場合、拡張スコープのトランザクションはコンテナとアプリケーション管理エンティティーマネージャーの両方でサポートされます。しかし Web 専用アプリケーションでは、拡張スコープのトランザクションはアプリケーション管理エンティティーマネージャーでしかサポートできません。

Web 専用アプリケーションでは、コンテナ管理エンティティーマネージャーはトランザクションスコープの持続性コンテキストしか使用できないため、持続性コンテキストの種類を指定する必要はありません。 これは、EJB を利用した Web アプリケーションとは異なります。EJB を利用した Web アプリケーションでは、@PersistenceContext(type=PersistenceContextType.EXTENDED) または @PersistenceContext(type=PersistenceContextType.TRANSACTION) によってエンティティーマネージャーが拡張スコープの持続性コンテキストを使用するよう指定できます。Web 専用アプリケーションの場合、注釈を使って持続性コンテキストの種類を設定してはいけません。

Web 専用アプリケーションの場合に、制約事項として知っておくべきことは、アプリケーション管理のトランザクションしか使用できないことです。ステートレスおよびステートフルセッション Bean を使ってエンティティーにアクセスする EJB モジュールを持つ Web アプリケーションは、コンテナ管理トランザクションを使用できます。コンテナ管理トランザクションにより Java EE プラットフォームはトランザクションのライフサイクルに対処できます。これに対し、Web 専用アプリケーションの場合、コンテナ管理トランザクションは選択肢になりません。 このことは、プログラム的な API を使って、トランザクションの開始とコミット、管理を区分 (demarcate) することを意味します。

Web 専用アプリケーションで Java Persistence を使用するこれら 2 つの方法を見てみましょう。一方はアプリケーション管理エンティティーマネージャーを使用し、もう一方はコンテナ管理エンティティーマネージャーを使用します。

1. アプリケーション管理エンティティーマネージャーの使用

最初に、アプリケーション管理エンティティーマネージャーを利用したアプリケーションのデザインを見てみましょう。エンティティーマネージャーファクトリの一般的な使い方は、PersistenceUnit 注釈を使って注入する方法です。たとえば、@PersistenceUnit(unitName="CatalogPu") EntityManagerFactory emf です。コードは、そのエンティティーマネージャーを使い終わるまでファクトリを保持して、再利用できるようにします。EntityManagerFactory はスレッドセーフなアプリケーションスコープで保持できるため、アプリケーション管理エンティティーマネージャーを使った Web 専用アプリケーションでは、アプリケーションスコープで EntityManagerFactory をキャッシュし、要求間で共有されるようにします。アプリケーションがエンティティーマネージャーファクトリを閉じると、そのすべてのエンティティーマネージャーが閉じた状態であるとみなされます。

アプリケーション管理エンティティーマネージャー用の EntityManager インタフェースを見てみましょう。 EntityManager インタフェースには、オブジェクトを持続させ、クエリーなどを実行するためのメソッドがあります。また、コンテナ管理エンティティーマネージャーを使用していないため、コードで呼び出す必要があるライフサイクルメソッドもいくつかあります。 アプリケーション管理エンティティーマネージャーの場合のエンティティーマネージャーの取得方法を見てみましょう。アプリケーション管理エンティティーマネージャーの使用時は、EntityManagerFactory.createEntityManager() メソッドを使ってエンティティーマネージャーを取得します。 ファクトリを使ってエンティティーマネージャーを取得するということは、それらエンティティーマネージャーをアプリケーションで管理することの指定にもなります。アプリケーション管理エンティティーマネージャーの場合、注入や JNDI ルックアップを使ってエンティティーマネージャーを取得することはできません。エンティティーマネージャーの取得には、EntityManagerFactory.createEntityManager() を使用する必要があります。

コード例 1 は、エンティティーマネージャーファクトリを正しく取得する方法を示しています。emf 参照をインスタンス変数として保持し、その他のメソッド呼び出しで再利用できます。アプリケーション管理エンティティーマネージャーの使用法は、ファクトリを使用して EntityManager em = emf.createEntityManager() への呼び出しでエンティティーマネージャーを取得することで指定します。 これで、エンティティーマネージャー API を使用して、エンティティーを持続させたり、エンティティーに問い合わせたりできます。

@PersistenceUnit private EntityManagerFactory emf; //再利用するためインスタンス変数として宣言。
...
//このメソッドはエンティティーマネージャーを使ってエンティティー Item.java を持続させる
public void addItem(Item item){
  EntityManager em = emf.createEntityManager();
  try {
    ...
    em.persist(item);
  ...
  } finally {
    em.close();
  }

コード例 1: アプリケーション管理エンティティーマネージャーの取得と利用

アプリケーションコードはまた、エンティティーマネージャーのライフサイクルを管理する必要があります。アプリケーション管理エンティティーマネージャーの使用時は、closeisOpen、および joinTransaction メソッドを使って、エンティティーマネージャーとそのライフサイクルを管理します。また、アプリケーションは、使い終えた時にエンティティーマネージャーを閉じる必要があります。EntityManager はスレッドセーフではないため、スレッドセーフではない方法で EntityManager のインスタンスに対する参照を保持してはいけません。 アプリケーション管理エンティティーマネージャーは拡張スコープの持続性コンテキストを持つため、複数の要求に対して同じエンティティーマネージャーを開いたままにし、処理が終了した時点でそのエンティティーマネージャーを閉じることができます。アプリケーション管理エンティティーマネージャーはまた、トランザクションスコープのエンティティーマネージャーの振る舞いを真似ることができ、それを作成した同じメソッドの終了時にエンティティーマネージャーを閉じることができます。このようにして、拡張スコープの持続性コンテキストは、トランザクションスコープの持続性コンテキストの振る舞いを実現したり、複数の HTTP 要求、複数のトランザクションにまたがることができるため、自由度の面で優れています。エンティティーマネージャーを開いたままにして、拡張スコープの持続性コンテキストを利用するケースとしては、いくつかのフォーム送信を使ってエンティティー内のすべてのユーザー情報を収集する複数ページからなるフォームがあります。ユーザー情報の収集を終えたあと、すべてのエンティティーをフラッシュし、それらエンティティーをデータベースに送信できます。

javax.persistence.EntityManager インタフェース内のメソッドには、トランザクションコンテキスト内での呼び出し必要とするものがあります。トランザクションがなく持続性コンテキストがトランザクションスコープの場合、そうしたメソッドは TransactionRequiredException をスローします。データベースにデータを書き込んだり、データを更新したりします。そうしたメソッドを呼び出す前には、トランザクションコンテキストが存在することを事前に確認する必要があります。 persistmergeremoveflush、および refresh メソッドがこれに該当します。
もう 1 つ、アプリケーション管理エンティティーマネージャーに関するデザイン上の課題として、JTA トランザクションまたはローカルリソーストランザクションのどちらを使用するかの選択があります。Web 専用アプリケーションでは、アプリケーション管理トランザクションを使用する必要があります。このことは、プログラム的な API を使って、トランザクションの開始とコミットを区分することを意味します。しかし、アプリケーション管理エンティティーマネージャーには、コンテナ管理エンティティーマネージャーにない柔軟性があります。アプリケーション管理エンティティーマネージャーでは、JTA エンティティーマネージャーまたはリソースにローカルのエンティティーマネージャーのどちらを使用するかはデザイン時の決定になっています。JTA エンティティーマネージャーのトランザクションは JTA を使って制御されます。 アプリケーション管理エンティティーマネージャーは、JTA またはリソースにローカルのどちらでもかまいません。 コード例 2 は、JTA アプリケーション管理エンティティーマネージャーと、リソースにローカルのアプリケーション管理エンティティーマネージャーのコードを比較しています。

JTA エンティティーマネージャーのコード リソースにローカルのエンティティーマネージャーのコード
@PersistenceUnit EntityManagerFactory emf;
@Resource UserTransaction utx;
 
...
public void addItem(Item item){
  EntityManager em = emf.createEntityManager();
  try {
    utx.begin();
    em.joinTransaction();
    em.persist(item);
    utx.commit();
  } catch(Exception exe){
    System.out.println("Error persisting item: "+exe);
    try {
      utx.rollback();
    } catch (Exception e) {}
  } finally {
    em.close();
 }
}
...
@PersistenceUnit private EntityManagerFactory emf;
...
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//persist などのその他のエンティティーマネージャーオペレーションを呼び出す
...
em.getTransaction().commit();
em.close();
コード例 2: JTA エンティティーマネージャーとリソースにローカルのエンティティーマネージャーのコード比較

一般には、JTA を利用する方が好ましい選択です。JTA を使用することで、複数のオペレーションをそのトランザクション区分内に置いて不可分にできます。リソースローカルの方は、いくぶん性能上のメリットがあります。また、オペレーションが 1 つだけの単純なケースで、あるいは 1 つのメソッド内の 2 つのオペレーションで処理対象のトランザクションが異なる場合に役立つことがあります。

2. コンテナ管理エンティティーマネージャーの使用

Web アプリケーションでは、JNDI ルックアップを使ってエンティティーマネージャーを取得するか、@PersistenceContext 注釈を使ってエンティティーマネージャーを注入することで、コンテナ管理エンティティーマネージャーを使用することを指定できます。この結果、エンティティーマネージャーは Java EE 5 コンテナによって管理されます。コンテナ管理エンティティーマネージャーの場合、アプリケーションコードがエンティティーマネージャーファクトリと対話することはなく、代わりに注入またはルックアップを使用するだけです。コンテナ管理エンティティーマネージャーがあると、コンテナは EntityManager インスタンスを閉じるなどのアクションのためにライフサイクルメソッドを呼び出します。このため、コードは少し簡素化されます。コードは単にエンティティーマネージャーを注入して使用し、呼び出し元メソッドの最後で、コンテナがエンティティーマネージャーを閉じます。

エンティティーマネージャーはスレッドセーフではないため、保持しようとしたり、再利用したりしてはいけません。エンティティーマネージャーは同じメソッド内で作成し、閉じる必要があります。 たとえばサーブレット内で EntityManager をインスタンス変数として宣言した場合、複数の並行要求が同時にそのエンティティーマネージャーにアクセスし、スレッドセーフでない動作が発生することがあります。ただし、これには例外があります。たとえば、要求スコープで指定された JSF 管理 Bean などの要求スコープの Web コンポーネントは、エンティティーマネージャーをインスタンス変数として宣言し、注入してその値を設定できます。要求スコープを持つ JSF 管理 Bean の場合、要求の最後でオブジェクトが破壊されるため、エンティティーマネージャーが再利用されることはなく、スレッドセーフです。しかし、これは珍しいケースで、より一般的な対処法を検討することが求められます。Web コンポーネントで注釈を使い、スレッドセーフな方法でエンティティーマネージャーを注入することはできないため、Web 専用アプリケーションでコンテナ管理エンティティーマネージャーを使用するのは少し面倒なことになります。エンティティーマネージャーはスレッドセーフではないため、たいていの Web コンポーネントは、メソッドのスコープ内で EntityManagers を宣言する必要があります。しかし、メソッド内で注釈を適用することはできないため、メソッド内で EntityManager を宣言した場合、注入を使用することはできません。このことは、JNDI ルックアップを使ってエンティティーマネージャーのインスタンスを取得しなければならないことを意味します。次のコード例は、注釈を使用して、注釈を利用する依存性を宣言し (配備記述子の使用を回避)、JNDI ルックアップを使ってエンティティーマネージャーを取得する方法を示しています。 これは、Web 専用 (EJB モジュールを持たない) アプリケーションにおいてコンテナ管理エンティティーマネージャーを使用する、スレッドセーフな方法です。コード例 3 は、適切なスレッドセーフな方法と悪い例とを表した、エンティティーマネージャーを使用する代わりの方法を示しています。

悪い例: スレッドセーフでないため、良くない

良い例: スレッドセーフ。
注釈を使用して、最初に JNDI 依存性を宣言。JNDI ルックアップを使用して、エンティティーマネージャーを取得。
public class MyServlet extends HttpServelet {

  @PersistenceContextEntityManager em;
 
  public void doGet( HttpServletRequest req,
     HttpServletResponse resp) throws ... {
    ...
    //並行スレッドが同じ
    //エンティティーマネージャーインスタンス
    //を使用する可能性があることに注意
    em.persist(item)
    
....エンティティーマネージャーを使用するコードが続く
  }
}
 


@PersistenceContext(name="foo", unitName="myPuName")
public class MyServlet extends HttpServelet {

  public void doGet(HttpServletRequest req,
     HttpServletResponse resp) throws ... {
    ...
    // すべての doGet メソッドが
    //それぞれ専用のエンティティーマネージャーを取得するため、
    //このメソッドスコープの em はスレッドセーフ。
    //
また、JNDI ルックアップでの em 取得にも注目
    EntityManager em = (EntityManager) ic.lookup("java:comp/env/foo");
    em.persist(item)
    
....エンティティーマネージャーを使用するコードが続く
  }
}

コード例 3: EJB モジュールを持たない Web 専用アプリケーションで、コンテナ管理エンティティーマネージャーを使用するスレッドセーフな方法

Web 専用 (EJB モジュールなし) アプリケーションがコンテナ管理の拡張持続性コンテキストを持つことはできません。このため、コンテナ管理エンティティーマネージャーを使用する場合は、トランザクションスコープの持続性コンテキストしか利用できません。これは、ステートフルセッション Bean のように、コンテナが close() を呼び出す方法がないためです。拡張スコープの持続性コンテキストが必要な場合は、アプリケーション管理エンティティーマネージャーの使用を検討するか、EJB モジュールを導入し、ステートフルセッション Bean を使用することを検討してください。そうすることで、コンテナ管理エンティティーマネージャーと拡張スコープの持続性コンテキストの組み合わせが可能になります。

Java Persistence 仕様によれば、コンテナ管理エンティティーマネージャーは JTA エンティティーマネージャーである必要があります。JTA エンティティーマネージャーは現在の JTA トランザクションに参加します。アプリケーション管理エンティティーマネージャーと異なり、リソースにローカルのトランザクションは使用できません。このことは、Web 専用アプリケーションがトランザクションを区分するのにアプリケーションコード内で JTA を使用する必要があることを意味します。コード例 4 は、JTA とコンテナ管理エンティティーマネージャーを組み合わせて、トランザクションを区分する正しい方法を示しています。

@PersistenceContext(name="foo", unitName="myPuName")
public class MyServlet extends HttpServelet {

@Resource UserTransaction utx;

  public void doGet(HttpServletRequest req,
                    HttpServletResponse resp) throws throws ServletException, IOException {

    EntityManager em = (EntityManager) ic.lookup("java:comp/env/foo");

    //要求からデータを取得
    String name = request.getParameter("item_name");
    ...
    //新規 Item エンティティーを作成
    Item item = new Item();
    //Item エンティティーの値を設定
    item.setName(name);
    ...
    utx.begin();
    em.persist(item); //新規 Item を持続させ、データベースに追加
    utx.commit();
    ...
}

コード例 4: Web 専用アプリケーションでコンテナ管理エンティティーマネージャーを使用する正しい方法

コード例 4 に示しているように、コンテナ管理エンティティーマネージャーを使用するのはかなり簡単です。 エンティティーマネージャーがコンテナ管理されるため、そのライフサイクルを管理するための明示的な呼び出しはありません。@Resource 注釈によって、JTA トランザクションを制御するための UserTransaction インスタンスを注入します。UserTransaction はスレッドセーフであるため、ほかの要求と共有されるインスタンス変数として保持できます。ここではデータベース書き込みを行うため、em.persist() を呼び出す直前にトランザクションを開始し、あとでコミットします。コンテナはこのエンティティーマネージャーのライフサイクルを管理し、メソッドの最後で閉じます。サーブレットクラスに、持続性コンテキストで依存性を宣言するための @PersistenceContext(name="foo", unitName="myPuName") 注釈があることに注目してください。このあと、Servlet.doGet メソッド内で JNDI を使ってエンティティーマネージャーをルックアップし、取得しています (EntityManager em = ic.lookup("java:comp/env/foo"))

参考資料

次に参考資料を挙げます。

© Sun Microsystems 2006. Java BluePrints Solutions Catalog の内容はすべて著作権保護されており、サン・マイクロシステムズ社の書面による許可なしに他の著作物に発表することを禁止します。