2つの異なるデータベースサーバー(H2とOracle)にアクセスする必要があるWebアプリケーションを開発しています。コンテナは Apache Tomee 1.5.1 であり、Java EEスタックとそれに提供されるライブラリ(JSF、JPA、CDI、EJBなど)を使用しています。 )。
XAトランザクション内で2つのエンティティマネージャーを使用してOracleデータベースからデータを抽出し、変換後にH2に永続化しようとしていますが、使用するエンティティマネージャーに関係なく、すべてのクエリがH2データベースに対して実行されます。何か助け?
[〜#〜] edit [〜#〜]:エンティティマネージャーに逆の順序でアクセスしようとすると、動作は同じであることがわかりましたしかし、Oracleへのアクセス。つまり、エンティティマネージャーは最初にアクセスしたデータベースにとどまります。
これが発生するEJB(JSFからservice.getFoo()
を呼び出す):
@Named
@Stateless
public class Service {
@Inject
@OracleDatabase
private EntityManager emOracle;
@Inject
@H2Database
private EntityManager emH2;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public List<Foo> getFoo() {
TypedQuery<Foo> q = emH2.createQuery(
"SELECT x FROM Foo f", Foo.class);
List<Foo> l = q.getResultList();
if (l == null || l.isEmpty()) {
update();
}
return q.getResultList();
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void update() {
// FAIL: This query executes against H2 with Oracle entity manager!
List<Object[]> l = emOracle.createNativeQuery("SELECT * FROM bar ").getResultList();
//more stuff...
}
}
エンティティマネージャのリソースプロデューサ(CDI)(@ H2Databaseと@OracleDatabaseは qualifiers です):
public class Resources {
@Produces
@PersistenceContext(unitName = "OraclePU")
@OracleDatabase
private EntityManager emOracle;
@Produces
@PersistenceContext(unitName = "H2PU")
@H2Database
private EntityManager emH2;
}
私のperistence.xmlは次のようになります:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://Java.Sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://Java.Sun.com/xml/ns/persistence http://Java.Sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="H2PU"
transaction-type="JTA">
<provider>org.Apache.openjpa.persistence.PersistenceProviderImpl</provider>
<jta-data-source>H2DS</jta-data-source>
<class>my.app.h2.Foo</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
<persistence-unit name="OraclePU" transaction-type="JTA">
<provider>org.Apache.openjpa.persistence.PersistenceProviderImpl</provider>
<jta-data-source>OracleDS</jta-data-source>
<class>my.app.Oracle.Bar</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
</persistence>
最後に、tomee.xml内のデータソース(このファイル内で構成された他のデータソースはありません):
<Resource id="OracleDS" type="javax.sql.DataSource">
jdbcDriver = Oracle.jdbc.xa.client.OracleXADataSource
jdbcUrl = jdbc:Oracle:thin:@server:port:instance
jtaManaged = true
password = abcde
userName = user
</Resource>
<Resource id="H2DS" type="javax.sql.DataSource">
jdbcDriver=org.h2.jdbcx.JdbcDataSource
jdbcUrl=jdbc:h2:h2/db;AUTO_SERVER=TRUE
jtaManaged = true
password = edcba
userName = user
</Resource>
コンテナ管理の永続コンテキスト
(@PersistenceContextアノテーションを使用する場合のように)コンテナ管理の永続コンテキストを使用する場合、JPA仕様では、JTAトランザクションに関連付けることができる永続コンテキストは1つだけであると指定されています。
永続化コンテキストはJava EEコンテナによって作成されます。コードの外観にもかかわらず(@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は、各メソッドでプライベートに宣言できます。実装ロジックは非常に単純です。
問題への回答:アプリケーション管理の永続コンテキストを使用(アプリケーション管理のEntityManagers経由)
次のいずれかの方法でentityManagerを宣言します。
// "Java EE style" declaration of EM
@PersistenceUnit(unitName="H2PU")
EntityManagerFactory emfH2;
EntityManager emH2 = emfH2.createEntityManager();
OR
// "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()を呼び出す必要があります。できれば、最後の{}句またはJava 7 try-with-resourcesステートメントを使用するのが望ましいです。
アプリケーション管理のEMは、引き続きJTAトランザクションに参加(つまり、同期)します。任意の数のアプリケーション管理のEMが1つのJTAトランザクションに参加できますが、これらの永続性コンテキストに関連付けられた永続性コンテキストやコンテナー管理されたEM。
EntityManagerがJTAトランザクションのコンテキスト外で(トランザクションが開始する前に)作成された場合、JTAトランザクションに参加することを明示的に要求する必要があります。
// must be run from within Java EE code scope that already has a JTA
// transaction active:
em.joinTransaction();
さらに簡単に言えば、JTAトランザクションのコンテキスト内でEntityManagerが作成された場合、アプリケーション管理の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
これは推奨されるJava EEアプローチです。
EJBクラス@TransactionManagementアノテーション:
明示的にTransactionManagement.CONTAINERに設定するか、省略して暗黙的にデフォルト値を使用する必要があります。
EJBメソッド@TransactionAttribute注釈:TransactionAttribute.REQUIREDを明示的に設定するか、省略して暗黙的にデフォルト値を使用する必要があります。 (注:別のビジネスシナリオがある場合、セマンティクスがニーズに一致した場合、TransactionAttribute.MANDATORYまたはTransactionAttribute.REQUIRES_NEWを使用できます。)
アプリケーション管理エンティティマネージャー:
上記のように、Persistence.createEntityManagerFactory( "unitName")およびemf.createEntityManager()を介して作成する必要があります。
JTAトランザクションを使用してEntityManagersに参加します。
トランザクションEJBメソッド内でEntityManagerを作成すると、自動的にJTAトランザクションに参加します。 OR EntityManagersが事前に作成されている場合は、トランザクション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を作成しますOR call 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)POJO /非EJB、ハンドコーディング(Bean管理)リソースローカルトランザクション(JTAではない)
Tx境界設定には、JPA EntityTransactionインターフェースを使用するだけです(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 /非EJB
これは、POJO /コンポーネントがJTAをサポートするコンテナで実行されていることを前提としています。
Java EEコンテナの場合、Java JTA UserTransactionインターフェースのEEリソースインジェクションを使用できます。
(または、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
// ...
}
// ...
}
}