私はJava EE 7を使用しています。JPAEntityManager
をアプリケーションスコープ CDI Beanに挿入する適切な方法を知りたいのですが。 。EntityManager
インスタンスはスレッドセーフではないため、@PersistanceContext
アノテーションを使用して単に注入することはできません。EntityManager
をすべてのHTTPの開始時に作成するとします。リクエストが処理され、HTTPリクエストが処理された後に閉じられます。2つのオプションが思い浮かびます:
1. EntityManager
への参照を持つリクエストスコープのCDI Beanを作成し、そのBeanをアプリケーションスコープのCDI Beanに挿入します。
import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@RequestScoped
public class RequestScopedBean {
@PersistenceContext
private EntityManager entityManager;
public EntityManager getEntityManager() {
return entityManager;
}
}
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@ApplicationScoped
public class ApplicationScopedBean {
@Inject
private RequestScopedBean requestScopedBean;
public void persistEntity(Object entity) {
requestScopedBean.getEntityManager().persist(entity);
}
}
この例では、EntityManager
はRequestScopedBean
が作成されるときに作成され、RequestScopedBean
が破棄されるときに閉じられます。これで、インジェクションをいくつかの抽象クラスに移動して、ApplicationScopedBean
から削除できます。
2. EntityManager
のインスタンスを生成するプロデューサーを作成してから、EntityManager
インスタンスをアプリケーションスコープのCDI Beanに挿入します。
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class EntityManagerProducer {
@PersistenceContext
@Produces
@RequestScoped
private EntityManager entityManager;
}
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
@ApplicationScoped
public class ApplicationScopedBean {
@Inject
private EntityManager entityManager;
public void persistEntity(Object entity) {
entityManager.persist(entity);
}
}
この例では、すべてのHTTPリクエストで作成されるEntityManager
も使用しますが、-closing the EntityManager
? HTTPリクエストが処理された後も閉じられますか? @PersistanceContext
アノテーションがコンテナ管理のEntityManager
を注入することは知っています。つまり、クライアントBeanが破棄されると、EntityManager
が閉じられます。この状況でのクライアントBeanとは何ですか? ApplicationScopedBean
ですか、アプリケーションが停止するまで破棄されませんか、それともEntityManagerProducer
ですか?何かアドバイスは?
アプリケーションスコープのBeanの代わりにステートレスEJBを使用し、EntityManager
を@PersistanceContext
アノテーションで注入することもできますが、それが目的ではありません:)
CDIプロデューサーとほぼ同じです。唯一のことは、プロデューサーフィールドの代わりにプロデューサーメソッドを使用する必要があることです。
WeldをCDIコンテナー(GlassFish 4.1およびWildFly 8.2.0)として使用している場合、プロデューサー field で@Produces
、@PersistenceContext
、および@RequestScoped
を組み合わせた例は、デプロイメント中にこの例外をスローする必要があります:
org.jboss.weld.exceptions.DefinitionException:WELD-001502:リソースプロデューサーフィールド[リソースプロデューサーフィールド[EntityManager]、修飾子[@Any @Default]が[[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducerとして宣言されています。 entityManager]]は@Dependentスコープでなければなりません
Java EEリソースを検索するためにプロデューサーフィールドを使用する場合、コンテナーは@Dependent以外のスコープをサポートする必要がないことがわかります。
CDI 1.2、セクション3.7。リソース:
コンテナは、@ Dependent以外のスコープを持つリソースをサポートする必要はありません。ポータブルアプリケーションでは、@ Dependent以外のスコープでリソースを定義しないでください。
この引用はすべて生産者分野に関するものでした。プロデューサーメソッドを使用してリソースを検索することは完全に合法です。
public class EntityManagerProducer {
@PersistenceContext
private EntityManager em;
@Produces
@RequestScoped
public EntityManager getEntityManager() {
return em;
}
}
まず、コンテナがプロデューサをインスタンス化し、コンテナ管理のエンティティマネージャ参照がem
フィールドに挿入されます。次に、コンテナーはプロデューサーメソッドを呼び出し、要求スコープのCDIプロキシーでプロデューサーメソッドをラップします。このCDIプロキシは、@Inject
を使用するときにクライアントコードが取得するものです。プロデューサークラスは@Dependent(デフォルト)であるため、基になるコンテナー管理エンティティマネージャー参照は、生成された他のCDIプロキシと共有されません。別のリクエストでエンティティマネージャーが必要になるたびに、プロデューサークラスの新しいインスタンスがインスタンス化され、新しいエンティティマネージャーの参照がプロデューサーに注入され、プロデューサーは新しいCDIプロキシでラップされます。
技術的に正しいため、フィールドem
へのリソースインジェクションを行う基になる名前のないコンテナは、古いエンティティマネージャを再利用できます(JPA 2.1仕様の脚注、セクション「7.9.1コンテナの責任」、357ページを参照)。 )。しかし、これまでのところ、JPAが必要とするプログラミングモデルを尊重しています。
上記の例では、EntityManagerProducer
@Dependentまたは@RequestScopedをマークしても問題ありません。 @Dependentを使用すると、意味的にはより正確になります。ただし、プロデューサークラスにリクエストスコープよりも広いスコープを設定すると、基になるエンティティマネージャーの参照を多くのスレッドに公開してしまうリスクがあります。基になるエンティティマネージャーの実装はおそらくスレッドローカルオブジェクトですが、移植可能なアプリケーションは実装の詳細に依存できません。
CDIは、要求にバインドされたコンテキストに入れたものを閉じる方法を知りません。何よりも、コンテナ管理のエンティティマネージャーはアプリケーションコードで閉じてはなりません。
JPA 2.1、セクション「7.9.1コンテナの責任」:
アプリケーションがコンテナ管理のエンティティマネージャでEntityManager.closeを呼び出す場合、コンテナはIllegalStateExceptionをスローする必要があります。
残念ながら、多くの人は@Disposes
メソッドを使用してコンテナ管理のエンティティマネージャーを閉じます。オラクルによって提供される公式の Java EE 7チュートリアル および CDI仕様 自体がディスポーザーを使用してコンテナー管理エンティティーマネージャーを閉じるときに、誰が責任を負うことができますか。これは単に誤りであり、EntityManager.close()
への呼び出しは、ディスポーザメソッドまたは他のどこで、その呼び出しをどこに置いても、IllegalStateException
をスローします。 Oracleの例は、プロデューサークラスを@javax.inject.Singleton
として宣言することによる2つの最大の罪です。私たちが学んだように、このリスクは、基になるエンティティマネージャーの参照を多くの異なるスレッドに公開するリスクです。
here CDIプロデューサーとディスポーザーを誤って使用すると、1)スレッドセーフでないエンティティマネージャーが多くのスレッドにリークされる可能性があり、2)ディスポーザーが効果がないことが証明されています。エンティティマネージャを開いたままにします。起こったのはIllegalStateExceptionで、コンテナはそれを痕跡なく残して飲み込んでいました(「インスタンスの破壊エラー」があったという不思議なログエントリが作成されます)。
一般に、CDIを使用してコンテナー管理エンティティーマネージャーを検索することはお勧めできません。アプリケーションは、@PersistenceContext
を使用するだけで十分満足でき、満足できるでしょう。ただし、例のようにルールには常に例外があり、CDIはアプリケーション管理エンティティマネージャーのライフサイクルを処理するときにEntityManagerFactory
を抽象化するのにも役立ちます。
コンテナ管理のエンティティマネージャーを取得する方法とCDIを使用してエンティティマネージャーを検索する方法の詳細については、 [] および this を参照してください。
あなたの問題を理解しました。しかし、それは本物ではありません。含まれているクラスのCDI宣言されたスコープをめちゃくちゃにしないでください。@ Inject'ionを使用するものを想定して、属性のスコープが伝播されます!
@ Inject'edは、実装クラスのCDI宣言に依存して参照を計算します。したがって、内部に@Inject EntityManager emが含まれるApplicationscopedクラスがある可能性がありますが、実装クラスのEntityManager CDI宣言が背後にあるため、制御フローごとに、異なるemオブジェクトへの独自のemトランザクション参照が見つかります。
コードの間違った点は、内部のgetEntityManager()アクセスメソッドを提供することです。 Injectedオブジェクトを渡さないでください。必要な場合は、@ Inject itを渡してください。
以下の例のように、@Dispose
アノテーションを使用してEntityManager
を閉じる必要があります。
@ApplicationScoped
public class Resources {
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;
@Produces
@Default
@RequestScoped
public EntityManager create() {
return this.entityManagerFactory.createEntityManager();
}
public void dispose(@Disposes @Default EntityManager entityManager) {
if (entityManager.isOpen()) {
entityManager.close();
}
}
}