コンテナ管理による永続性コンテキスト
コンテナ管理の永続コンテキストを使用する場合(@PersistenceContextアノテーションを使用する場合)、JPA仕様では、1つの永続コンテキストのみをJTAトランザクションに関連付けることができると指定されています。
永続コンテキストは、JavaEEコンテナーによって作成されます。コードの外観(@PersistenceContextアノテーションは、PCがEntityManagerインスタンス変数に直接注入されることを示唆しているようです)にもかかわらず、永続コンテキストは実際にはJTAトランザクション内の参照として格納されます。EntityManager操作が発生するたびに、それ自体の内部永続コンテキストを参照しません。代わりに、コンテナ管理されているため、特別な操作を実行します。常にJTAトランザクション内の永続コンテキストを検索し、それを使用します。これは、JTA永続コンテキストの伝播と呼ばれます。
JPA仕様からの引用:
コンテナ管理のエンティティマネージャを使用する場合、永続コンテキストのライフサイクルは常にアプリケーションに対して透過的に自動的に管理され、永続コンテキストはJTAトランザクションで伝播されます。
コンテナ管理のトランザクションスコープの永続コンテキスト
...
新しい永続コンテキストは、アクティブなJTAトランザクションのスコープでコンテナ管理エンティティマネージャが呼び出されたときに開始され[76]、JTAトランザクションにすでに関連付けられている現在の永続コンテキストはありません。永続コンテキストが作成され、JTAトランザクションに関連付けられます。
コンテナ管理の拡張永続コンテキスト
...コンテナ管理の拡張永続コンテキストは、ステートフルセッションBeanのスコープ内でのみ開始できます。これは、タイプPersistenceContextType.EXTENDEDのエンティティマネージャへの依存関係を宣言するステートフルセッションBeanが作成された時点から存在し、ステートフルセッションBeanにバインドされていると言われます。拡張永続コンテキストへの依存関係は、PersistenceContextアノテーションまたはpersistence-context-refデプロイメント記述子要素によって宣言されます。ステートフルセッションBeanの@Removeメソッドが完了すると(またはステートフルセッションBeanインスタンスが破棄されると)、永続コンテキストはコンテナによって閉じられます。
永続コンテキストの伝播の要件
...コンポーネントが呼び出され、JTAトランザクションがない場合...、永続コンテキストは伝播されません。•PersistenceContext-Type.TRANSACTIONで定義されたエンティティマネージャを呼び出すと、新しい永続コンテキストが使用されます。•PersistenceContext-Type.EXTENDEDで定義されたエンティティマネージャを呼び出すと、そのコンポーネントにバインドされた既存の拡張永続コンテキストが使用されます。
...コンポーネントが呼び出され、JTAトランザクションがそのコンポーネントに伝播される場合:•コンポーネントが、拡張永続コンテキストがバインドされているステートフルセッションBeanであり、JTAトランザクションにバインドされた別の永続コンテキストがある場合、 EJBExceptionはコンテナによってスローされます。•それ以外の場合、JTAトランザクションにバインドされた永続コンテキストがある場合、その永続コンテキストが伝播されて使用されます。
それがあなたの問題です。明らかな64ドルの質問:なぜスペックはこれを要求するのですか?
それは、EJBに強力なEntityManagerの魔法をもたらすのは意図的なトレードオフだからです。
JTAトランザクションを使用して単一の永続コンテキストを伝播することには制限があります。トランザクションは複数の永続コンテキストにまたがることができないため、複数のデータベースにまたがることはできません。
ただし、これには大きな利点もあります。EJBで宣言されたentityManagerは、同じ永続コンテキストを自動的に共有できるため、同じJPAエンティティのセットで動作し、同じトランザクションに参加できます。複雑な他のEJBを呼び出すEJBのチェーンを持つことができ、それらはすべてJPAエンティティデータに対して適切かつ一貫して動作します。また、メソッド呼び出し間でエンティティマネージャー参照を一貫して初期化/共有するという複雑さも必要ありません。EntityManagerは各メソッドでプライベートに宣言できます。実装ロジックは非常に単純にすることができます。
問題への回答:アプリケーション管理の永続コンテキストを使用する(アプリケーション管理のEntityManagerを介して)
次のいずれかの方法でentityManagerを宣言します。
// "Java EE style" declaration of EM
@PersistenceUnit(unitName="H2PU")
EntityManagerFactory emfH2;
EntityManager emH2 = emfH2.createEntityManager();
また
// "JSE style" declaration of EM
EntityManagerFactory emfH2 = javax.persistence.Persistence.createEntityManagerFactory("H2PU");
EntityManager emH2 = emfH2.createEntityManager();
and the same for emfOracle & emOracle.
各EMが終了したら、em.close()を呼び出す必要があります。できれば最後の{}句またはJava7try-with-resourcesステートメントを使用してください。
アプリケーション管理のEMは、引き続きJTAトランザクションに参加します(つまり、同期します)。任意の数のアプリケーション管理EMが単一のJTAトランザクションに参加できますが、これらのいずれも、コンテナ管理EMに関連付けられたり伝播されたりする永続コンテキストを持つことはありません。
EntityManagerがJTAトランザクションのコンテキスト外で(トランザクションが開始される前に)作成された場合は、JTAトランザクションに参加するように明示的に要求する必要があります。
// must be run from within Java EE code scope that already has a JTA
// transaction active:
em.joinTransaction();
または、さらに簡単に言えば、EntityManagerがJTAトランザクションのコンテキスト内で作成された場合、アプリケーション管理のEntityManagerは自動的にJTAトランザクションに暗黙的に参加します。joinTransaction()は必要ありません。
したがって、アプリケーション管理のEMは、複数のデータベースにまたがるJTAトランザクションを持つことができます。もちろん、JTAとは独立してローカルリソースのJDBCトランザクションをいつでも実行できます。
EntityTransaction tx = em.getTransaction();
tx.begin();
// ....
tx.commit();
編集:アプリケーション管理エンティティマネージャーを使用したトランザクション管理の詳細
警告:以下のコードサンプルは教育用です-私のポイントを説明するために頭のてっぺんからタイプしましたが、コンパイル/デバッグ/テストする時間がありませんでした。
EJBのデフォルトの@TransactionManagementパラメーターはTransactionManagement.CONTAINERであり、EJBメソッドのデフォルトの@TransactionAttributeパラメーターはTransactionAttribute.REQUIREDです。
トランザクション管理には4つの順列があります。
A)CONTAINER管理のJTAトランザクションを使用するEJB
これは、推奨されるJavaEEアプローチです。
EJBクラス@TransactionManagementアノテーション:
デフォルト値を暗黙的に使用するには、TransactionManagement.CONTAINERに明示的に設定するか、省略しなければなりません。
EJBメソッド@TransactionAttributeアノテーション:デフォルト値を暗黙的に使用するには、TransactionAttribute.REQUIREDに明示的に設定するか、省略しなければなりません。(注:別のビジネスシナリオがある場合、セマンティクスがニーズに一致する場合は、TransactionAttribute.MANDATORYまたはTransactionAttribute.REQUIRES_NEWを使用できます。)
アプリケーション管理のエンティティマネージャー:
Persistence.createEntityManagerFactory( "unitName")およびemfを介して作成する必要があります。上記のように、.createEntityManager()。
JTAトランザクションでEntityManagersに参加します。
トランザクションEJBメソッド内にEntityManagerを作成すると、それらは自動的にJTAトランザクションに参加します。または、EntityManagerが事前に作成されている場合は、トランザクションEJBメソッド内でem.joinTransaction()を呼び出します。
それらの使用が終了したら、EntityManager.close()を呼び出します。必要なのはそれだけです。
基本的な例-複数のDB間でのトランザクションにさらに多くのEntityManagerを使用するだけです。
@Stateless
public class EmployeeServiceBean implements EmployeeService {
// Transactional method
public void createEmployee() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
EntityManager em = emf.createEntityManager();
Employee emp = ...; // set some data
// No need for manual join - em created in active tx context, automatic join:
// em.joinTransaction();
em.persist(emp);
// other data & em operations ...
// call other EJBs to partake in same transaction ...
em.close(); // Note: em can be closed before JTA tx committed.
// Persistence Context will still exist & be propagated
// within JTA tx. Another EM instance could be declared and it
// would propagate & associate the persistence context to it.
// Some time later when tx is committed [at end of this
// method], Data will still be flushed and committed and
// Persistence Context removed .
emf.close();
}
}
@Stateful
public class EmployeeServiceBean implements EmployeeService {
// Because bean is stateful, can store as instance vars and use in multiple methods
private EntityManagerFactory emf;
private EntityManager em;
@PostConstruct // automatically called when EJB constructed and session starts
public void init() {
emf = Persistence.createEntityManagerFactory("EmployeeService");
em = emf.createEntityManager();
}
// Transactional method
public void createEmployee() {
Employee emp = ...; // set some data
em.joinTransaction(); // em created before JTA tx - manual join
em.persist(emp);
}
// Transactional method
public void updateEmployee() {
Employee emp = em.find(...); // load the employee
// don't do join if both methods called in same session - can only call once:
// em.joinTransaction(); // em created before JTA tx - manual join
emp.set(...); // change some data
// no persist call - automatically flushed with commit
}
@Remove // automatically called when EJB session ends
public void cleanup() {
em.close();
emf.close();
}
// ...
}
B)BEAN管理のJTAトランザクションを使用したEJB
@TransactionManagement.BEANを使用します。
JTA UserTransactionインターフェースを注入して、BeanがJTAトランザクションを直接マークできるようにします。
UserTransaction.begin()/ commit()/ rollback()を使用して、トランザクションを手動でマーク/同期します。
EntityManagerがJTAトランザクションに参加していることを確認します-アクティブなJTAトランザクションコンテキストでEMを作成するか、em.joinTransaction()を呼び出します。
例:
@TransactionManagement(TransactionManagement.BEAN)
@Stateless
public class EmployeeServiceBean implements EmployeeService {
// inject the JTA transaction interface
@Resource UserTransaction jtaTx;
public void createEmployee() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
EntityManager em = emf.createEntityManager();
try {
jtaTx.begin();
try {
em.joinTransaction();
Employee emp = ...; // set some data
em.persist(emp);
// other data & em operations ...
// call other EJBs to partake in same transaction ...
} finally {
jtaTx.commit();
}
} catch (Exception e) {
// handle exceptions from UserTransaction methods
// ...
}
Employee emp = ...; // set some data
// No need for manual join - em created in active tx context, automatic join:
// em.joinTransaction();
em.persist(emp);
em.close(); // Note: em can be closed before JTA tx committed.
// Persistence Context will still exist inside JTA tx.
// Data will still be flushed and committed and Persistence
// Context removed some time later when tx is committed.
emf.close();
}
}
C)手動でコーディングされた(Bean管理された)リソースローカルトランザクション(JTAではない)を使用したPOJO / Non-EJB
txの境界設定にはJPAEntityTransactionインターフェースを使用するだけです(em.getTransaction()を介して取得)。
例:
public class ProjectServlet extends HttpServlet {
@EJB ProjectService bean;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
try {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
bean.assignEmployeeToProject(projectId, empId);
bean.updateProjectStatistics();
} finally {
tx.commit();
}
} catch (Exception e) {
// handle exceptions from EntityTransaction methods
// ...
}
// ...
}
}
D)手動でコード化された(POJO管理の)JTAトランザクションを使用するPOJO / Non-EJB
これは、POJO/コンポーネントがJTAをサポートするコンテナで実行されていることを前提としています。
Java EEコンテナ内の場合、JTAUserTransactionインターフェイスのJavaEEリソースインジェクションを使用できます。
(または、JTAインターフェースへのハンドルを明示的にルックアップして境界を設定し、em.getTransaction()。joinTransaction()を呼び出すことができます-JTA仕様を参照してください。)
例:
public class ProjectServlet extends HttpServlet {
@Resource UserTransaction tx;
@EJB ProjectService bean;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ...
try {
tx.begin();
try {
bean.assignEmployeeToProject(projectId, empId);
bean.updateProjectStatistics();
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
EntityManager em = emf.createEntityManager();
// Should be able to avoid explicit call to join transaction.
// Should automatically join because EM created in active tx context.
// em.joinTransaction();
// em operations on data here
em.close();
emf.close();
} finally {
tx.commit();
}
} catch (Exception e) {
// handle exceptions from UserTransaction methods
// ...
}
// ...
}
}