2013-10-17 9 views
18

Java EE應用程序到EntityManager的一個參考。您不能只使用@PersistanceContext註釋來注入它,因爲EntityManager實例不是線程安全的。假設我們希望我們的EntityManager在每個HTTP請求處理開始時創建,並在HTTP請求處理後關閉。我想到兩個選項:獲得我使用的Java EE 7,我想知道什麼是注入一個JPA <code>EntityManager</code>到<strong>應用程序的正確方法作用域</strong> CDI豆使用CDI

1. 創建一個引用EntityManager的請求範圍的CDI bean,然後將該bean注入到應用程序範圍的CDI bean中。當RequestScopedBean被破壞

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時將被創建,並且將被關閉。現在我可以將注入移動到某個抽象類,以將其從ApplicationScopedBean中移除。

2. 創建產生的EntityManager實例製片,然後注入EntityManager實例到應用程序作用域CDI豆。

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,但對於關閉EntityManager? HTTP請求處理後它是否也會關閉?我知道@PersistanceContext註釋注入容器管理的EntityManager。這意味着當客戶端bean被銷燬時,EntityManager將被關閉。在這種情況下,客戶端bean是什麼?它是ApplicationScopedBean,在應用程序停止之前它不會被銷燬,或者是EntityManagerProducer?任何建議?

我知道我可以使用無狀態EJB,而不是應用範圍的bean,然後就由@PersistanceContext註釋注入EntityManager,但是這不是問題的關鍵:)

回答

-1

您可能注入savely EntityManagerFactory的,它是線程保存

@PersistenceUnit(unitName = "myUnit") 
private EntityManagerFactory entityManagerFactory; 

然後您可以從entityManagerFactory中檢索EntityManager。

+0

我可以做到這一點,然後從工廠中的每個應用程序作用域bean的方法獲得'EntityManager'。但它會是一個容器管理的'EntityManager'嗎?我不這麼認爲。我不想管理'EntityManager'和我自己的事務的生命週期。 –

+0

@FlyingDumpling你使用的是什麼版本的java EE?如果在Java EE 7之前,無論如何您都沒有CDI bean中的事務支持。看到這個迴應:http://stackoverflow.com/questions/17838221/jee7-do-ejb-and-cdi-beans-support-container-managed-transactions –

+0

@AndreiI對不起,我沒有提到,我使用Java EE 7 –

0

您應該使用@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

您正在創建一個應用程序管理的實體管理器(必須由應用程序代碼關閉)。問題是關於容器管理的實體經理。 –

25

你幾乎就在您的CDI生產商。唯一的問題是你應該使用生產者方法而不是生產者領域。

如果您使用Weld作爲CDI容器(GlassFish 4.1和WildFly 8.2。0),那麼你的製作人組合@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方法來關閉容器管理實體管理器。當Oracle提供的官方Java EE 7 tutorial以及CDI specification本身使用disposer關閉容器管理的實體管理器時,誰能責怪他們。這簡直是​​錯誤的,無論你將該呼叫放在何處,在的呼叫將拋出一個IllegalStateException,在一個處置方法或其他地方。 Oracle的榜樣是兩個中最大的罪人,宣稱生產者類別爲@javax.inject.Singleton。正如我們所瞭解的,這種風險將底層實體經理引用到許多不同的線程中。

已經證明here,通過錯誤地使用CDI生產者和處置者,1)非線程安全的實體管理器可能泄露給許多線程,並且2)處置器沒有效果;讓實體經理開放。發生了什麼事是容器吞下的IllegalStateException不留下任何痕跡(一個神祕的日誌條目表明存在「摧毀實例的錯誤」)。

通常,使用CDI查找容器管理的實體管理器不是一個好主意。該應用程序最好使用@PersistenceContext,並對此感到滿意。但是,在您的示例中總是有例外規則,在處理應用程序管理的實體管理器的生命週期時,CDI也可以用於抽象出EntityManagerFactory

要獲取有關如何獲得容器管理的實體管理器以及如何使用CDI查找實體管理器,你可能需要閱讀thisthis一個完整的畫面。

相關問題