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

使用CDI在JavaEE应用程序中获取对EntityManager的引用,java,jpa,cdi,entitymanager,java-ee-7,Java,Jpa,Cdi,Entitymanager,Java Ee 7,我正在使用JavaEE7。我想知道将JPAEntityManager注入应用范围的cdibean的正确方法是什么。不能只使用@PersistanceContext注释注入它,因为EntityManager实例不是线程安全的。假设我们希望在每个HTTP请求处理开始时创建EntityManager,并在处理HTTP请求后关闭。我想到了两个选择: 一,。 创建一个请求范围的CDIBean,该CDIBean引用了EntityManager,然后将该bean注入到应用程序范围的CDIBean中 impor

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

一,。 创建一个请求范围的CDIBean,该CDIBean引用了
EntityManager
,然后将该bean注入到应用程序范围的CDIBean中

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.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class EntityManagerProducer {

    @PersistenceContext
    @Produces
    @RequestScoped
    private EntityManager entityManager;
}

在本例中,创建
RequestScopedBean
时将创建
EntityManager
,并在销毁
RequestScopedBean
时关闭。现在,我可以将注入移动到某个抽象类,以将其从
应用程序CopedBean
中删除

二,。 创建一个生成
EntityManager
实例的生产者,然后将
EntityManager
实例注入到应用程序范围的CDIBean中

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.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class EntityManagerProducer {

    @PersistenceContext
    @Produces
    @RequestScoped
    private EntityManager entityManager;
}

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


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

您可以注入savely EntityManager Factory,它是线程保存

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

然后您可以从EntityManager工厂检索EntityManager。

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

}

你和你的CDI制作人几乎是对的。唯一的问题是,您应该使用producer方法,而不是producer字段

如果您使用Weld作为CDI容器(GlassFish 4.1和WildFly 8.2.0),那么在producer字段上组合
@products
@PersistenceContext
@RequestScoped
的示例在部署期间应引发此异常:

org.jboss.weld.exceptions.DefinitionException:weld-001502: 资源生产者字段[资源生产者字段[EntityManager]与 限定符[@Any@Default]声明为[[BackedAnnotatedField] @生成@RequestScope@PersistenceContext com.somepackage.EntityManagerProducer.entityManager]]必须为 @从属作用域

事实证明,在使用producer字段查找JavaEE资源时,容器不需要支持@Dependent以外的任何其他作用域

CDI 1.2,第3.7节。资源:

容器不需要支持具有其他作用域的资源 而不是依赖。可移植应用程序不应定义资源 具有除@Dependent以外的任何作用域

这句话是关于生产者领域的。使用生产者方法查找资源是完全合法的:

public class EntityManagerProducer {

    @PersistenceContext    
    private EntityManager em;

    @Produces
    @RequestScoped
    public EntityManager getEntityManager() {
        return em;
    }
}
首先,容器将实例化生产者,容器管理的实体管理器引用将被注入
em
字段。然后容器将调用producer方法,并将其返回的内容封装在请求范围的CDI代理中。此CDI代理是客户端代码在使用
@Inject
时得到的。因为producer类是@Dependent(默认),所以生成的任何其他CDI代理都不会共享底层容器管理的实体管理器引用。每当另一个请求需要实体管理器时,producer类的一个新实例将被实例化,一个新的实体管理器引用将被注入到producer中,而producer又被包装在一个新的CDI代理中

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

在前面的示例中,将
EntityManagerProducer
@Dependent或@requestscope标记为Dependent并不重要。使用@Dependent在语义上更为正确。但是,如果在producer类上设置了比请求范围更宽的范围,那么就有可能将底层实体管理器引用暴露给许多线程,我们都知道这不是一件好事。底层实体管理器实现可能是一个线程本地对象,但可移植应用程序不能依赖于实现细节

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

JPA 2.1第7.9.1节“集装箱责任”:

如果应用程序 在容器管理的实体管理器上调用EntityManager.close

不幸的是,许多人确实使用
@Disposes
方法来关闭容器管理的实体管理器。当Oracle提供的官员以及其自身使用处置器关闭容器管理的实体管理器时,谁又能责怪他们呢。这完全是错误的,对
EntityManager.close()
的调用将抛出
IllegalStateException
,无论您将该调用放在何处,放在disposer方法中还是其他方法中