web-dev-qa-db-ja.com

CDIを使用したJava EEアプリケーションでのEntityManagerへの参照の取得

私は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);
    }
}

この例では、EntityManagerRequestScopedBeanが作成されるときに作成され、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アノテーションで注入することもできますが、それが目的ではありません:)

22
Flying Dumpling

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 を参照してください。

39

あなたの問題を理解しました。しかし、それは本物ではありません。含まれているクラスのCDI宣言されたスコープをめちゃくちゃにしないでください。@ Inject'ionを使用するものを想定して、属性のスコープが伝播されます!

@ Inject'edは、実装クラスのCDI宣言に依存して参照を計算します。したがって、内部に@Inject EntityManager emが含まれるApplicationscopedクラスがある可能性がありますが、実装クラスのEntityManager CDI宣言が背後にあるため、制御フローごとに、異なるemオブジェクトへの独自のemトランザクション参照が見つかります。

コードの間違った点は、内部のgetEntityManager()アクセスメソッドを提供することです。 Injectedオブジェクトを渡さないでください。必要な場合は、@ Inject itを渡してください。

1
Groovieman

以下の例のように、@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();
        }
    }

}
0
Claudio Miranda