使用CDI在Java EE应用程序中获取对EntityManager的引用

我正在使用Java EE 7.我想知道将JPA EntityManager注入到应用程序范围的 CDI bean中的正确方法是什么。 您不能只使用@PersistanceContext注释注入它,因为EntityManager实例不是线程安全的。 假设我们希望在每个HTTP请求处理的beginnig创建EntityManager ,并在处理HTTP请求后关闭它们。 我想到了两个选择:

1.创建一个请求范围的CDI bean,它具有对EntityManager的引用,然后将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); } } 

在此示例中,将在创建RequestScopedBean时创建EntityManager ,并在销毁RequestScopedBean时关闭EntityManager 。 现在,我可以将注入移动到一些抽象类,以将其从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); } } 

在这个例子中,我们还将有一个EntityManager ,它是在每个HTTP请求中创建的,但是关闭 EntityManager呢? 在处理HTTP请求后它是否也会关闭? 我知道@PersistanceContext注释注入容器管理的EntityManager 。 这意味着当客户端bean被销毁时, EntityManager将被关闭。 在这种情况下什么是客户端bean? 它是ApplicationScopedBean ,它在应用程序停止之前永远不会被销毁,还是EntityManagerProducer ? 有什么建议吗?

我知道我可以使用无状态EJB而不是应用程序作用域bean,然后通过@PersistanceContext注释注入EntityManager ,但这不是重点:)

你的CDI制作人几乎是对的。 唯一的事情是你应该使用生产者方法而不是生产者领域。

如果您使用Weld作为CDI容器(GlassFish 4.1和WildFly 8.2.0),那么在生产者字段上组合@Produces,@ PersistenceContext和@RequestScoped示例应该在部署期间抛出此exception:

org.jboss.weld.exceptions.DefinitionException:WELD-001502:资源生成器字段[资源生成器字段[EntityManager],其中限定符[@Any @Default]声明为[[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer。 entityManager]]必须是@Dependent作用域

事实certificate,当使用producer字段查找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字段。 然后容器将调用您的producer方法并将其返回的内容包装在请求范围的CDI代理中。 此CDI代理是您的客户端代码在使用@Inject时获得的代码。 由于生成器类是@Dependent(默认),因此生成的任何其他CDI代理都不会共享基础容器管理的实体管理器引用。 每当另一个请求想要实体管理器时,生成器类的新实例将被实例化,并且新的实体管理器引用将被注入到生成器中,而生成器又被包装在新的CDI代理中。

为了在技术上正确,允许将资源注入到字段em的基础和未命名容器重用旧的实体管理器(请参阅JPA 2.1规范中的脚注,“7.9.1容器责任”,第357页)。 但到目前为止,我们尊重JPA所要求的编程模型。

在前面的示例中,如果标记EntityManagerProducer @Dependent或@RequestScoped则无关紧要。 使用@Dependent在语义上更正确。 但是如果你在生产者类上放置比请求范围更宽的范围,那么冒险将底层实体管理器引用暴露给许multithreading,我们都知道这些线程并不是一件好事。 底层实体管理器实现可能是线程本地对象,但是可移植应用程序不能依赖于实现细节。

CDI不知道如何关闭你放入请求绑定上下文的任何东西。 更重要的是,容器管理的实体管理器不能被应用程序代码关闭。

JPA 2.1,“7.9.1容器责任”部分:

如果应用程序在容器管理的实体管理器上调用EntityManager.close,则容器必须抛出IllegalStateException。

不幸的是,许多人确实使用@Disposes方法来关闭容器管理的实体管理器。 当Oracle提供的官方Java EE 7教程以及CDI规范本身使用处理器关闭容器管理的实体管理器时,谁可以责怪他们。 这是完全错误的,对EntityManager.close()的调用将抛出IllegalStateException无论你将调用放在何处,在disposer方法中或其他地方。 通过将生产者类声明为@javax.inject.Singleton ,Oracle示例是两者中最大的罪人。 据我们了解,这种风险暴露了底层实体管理器对许多不同线程的引用。

这里已经certificate,通过错误地使用CDI生成器和处理器,1)非线程安全的实体管理器可能泄漏到许multithreading,2)处理器没有效果; 让实体经理开放。 发生了什么是容器吞没的IllegalStateException,没有留下任何痕迹(一个神秘的日志条目,说有一个“错误破坏一个实例”)。

通常,使用CDI查找容器管理的实体管理器并不是一个好主意。 只使用@PersistenceContext ,应用程序很可能会更好,并对它感到满意。 但是,在您的示例中,规则总是存在例外情况,并且在处理应用程序管理的实体管理器的生命周期时,CDI也可用于抽象出EntityManagerFactory

要全面了解如何获取容器管理的实体管理器以及如何使用CDI查找实体管理器,您可能需要阅读本文和此内容 。

我理解你的问题。 但那不是真的。 不要搞砸CDI声明的包含类的范围,这将传播属性的范围,期望那些使用@Inject’ion!

@Inject’ed将根据实现类的CDI声明的依赖性来计算它们的引用。 因此,您可能拥有带有@Inject EntityManager em的Applicationscoped类,但是每个控制流都会找到自己的em-transaction对disjount em-object的引用,因为后面的实现类的EntityManager CDI声明。

您的代码的错误在于,您提供了一个内部getEntityManager()访问方法。 不要传递Injected对象,如果需要,只需@Inject它。

您应该使用@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(); } } } 

你可以保存EntityManagerFactory,它是线程保存

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

然后你可以从entityManagerFactory中检索EntityManager。