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

Fly*_*ing 22 java jpa entitymanager cdi java-ee-7

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

1.创建一个请求范围的CDI bean,它具有对a的引用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;
    }
}
Run Code Online (Sandbox Code Playgroud)
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,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;
}
Run Code Online (Sandbox Code Playgroud)
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我知道我可以使用无状态EJB,而不是应用范围的bean,然后就注射EntityManager@PersistanceContext注释,但是这不是问题的关键:)

Mar*_*son 38

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

如果您使用焊接作为CDI容器(GlassFish的4.1和8.2.0 WildFly),那么你结合实例@Produces,@PersistenceContext@RequestScoped在生产现场部署过程中应该抛出此异常:

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

事实证明,当使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

首先,容器将实例化生产者,并将容器管理的实体管理器引用注入到em字段中.然后容器将调用您的producer方法并将其返回的内容包装在请求范围的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教程以及CDI规范本身使用处理器关闭容器管理的实体管理器时,谁可以责怪他们.这是完全错误的,无论你把电话放在哪里,在处理器方法或其他地方,调用EntityManager.close()都会抛出IllegalStateException.通过将生产者类声明为a,Oracle示例是两者中最大的罪人@javax.inject.Singleton.据我们了解,这种风险暴露了底层实体经理对许多不同线程的引用.

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

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

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

  • 老实说,为什么 SpringBoot 如此更好、更简单;-) 进行切换...... (3认同)

Pio*_*ski -1

你可以注入savely EntityManagerFactory,它是线程保存的

@PersistenceUnit(unitName = "myUnit")
private EntityManagerFactory entityManagerFactory;
Run Code Online (Sandbox Code Playgroud)

然后你可以从entityManagerFactory检索EntityManager。