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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19431423/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 17:05:41  来源:igfitidea点击:

Getting a reference to EntityManager in Java EE applications using CDI

javajpacdientitymanagerjava-ee-7

提问by Flying Dumpling

I'm using Java EE 7. I would like to know what is the proper way to inject a JPA EntityManagerinto an application scopedCDI bean. You can't just inject it using @PersistanceContextannotation, because EntityManagerinstances are not thread safe. Let's assume that we want our EntityManagerto be created at the beginnig of every HTTP request processing and closed after the HTTP request is processed. Two options come into my mind:

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

1. Create a request scoped CDI bean which has a reference to an EntityManagerand then inject the bean into an application scoped CDI bean.

1. 创建一个请求范围的 CDI bean,该 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);
    }
}

In this example an EntityManagerwill be created when the RequestScopedBeanis created, and will be closed when the RequestScopedBeanis destroyed. Now I could move the injection to some abstract class to remove it from the ApplicationScopedBean.

在这个例子中,anEntityManager将在创建时RequestScopedBean创建,并在RequestScopedBean销毁时关闭。现在我可以将注入移动到某个抽象类以将其从ApplicationScopedBean.

2. Create a producer that produces instances of EntityManager, and then inject the EntityManagerinstance into an application scoped CDI bean.

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

In this example we will also have an EntityManagerwhich is created every HTTP request, but what about closingthe EntityManager? Will it also be closed after the HTTP request is processed? I know that the @PersistanceContextannotation injects container-managed EntityManager. This means that an EntityManagerwill be closed when a client bean is destroyed. What is a client bean in this situation? Is it the ApplicationScopedBean, which will never be destroyed until the application stops, or is it the EntityManagerProducer? Any advices?

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

I know I could use a stateless EJB instead of application scoped bean and then just inject EntityManagerby @PersistanceContextannotation, but that's not the point :)

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

回答by Piotr Kochański

You may inject savely EntityManagerFactory, it is thread save

您可以注入安全的EntityManagerFactory,它是线程保存

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

then you can retrive EntityManager from entityManagerFactory.

然后您可以从 entityManagerFactory 检索 EntityManager。

回答by Claudio Miranda

You should use the @Disposeannotation to close the EntityManager, as in the example below:

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

}

回答by Martin Andersson

You're almost right on with your CDI producer. Only thing is that you should use a producer method instead of a producer field.

您的 CDI 制作人几乎是对的。唯一的问题是您应该使用生产者方法而不是生产者字段。

If you're using Weld as CDI container (GlassFish 4.1 and WildFly 8.2.0), then your example of combining @Produces, @PersistenceContextand @RequestScopedon a producer fieldshould throw this exception during deployment:

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

org.jboss.weld.exceptions.DefinitionException: WELD-001502: Resource producer field [Resource Producer Field [EntityManager] with qualifiers [@Any @Default] declared as [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer.entityManager]] must be @Dependent scoped

org.jboss.weld.exceptions.DefinitionException: WELD-001502: 资源生产者字段 [Resource Producer Field [EntityManager] 与限定符 [@Any @Default] 声明为 [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer。 entityManager]] 必须是 @Dependent 范围

Turns out that the container is not required to support any other scope than @Dependent when using a producer field to lookup Java EE resources.

事实证明,当使用生产者字段查找 Java EE 资源时,容器不需要支持除 @Dependent 之外的任何其他范围。

CDI 1.2, section 3.7. Resources:

CDI 1.2,第 3.7 节。资源:

The container is not required to support resources with scope other than @Dependent. Portable applications should not define resources with any scope other than @Dependent.

容器不需要支持@Dependent 以外范围的资源。便携式应用程序不应定义@Dependent 以外的任何范围的资源。

This quote was all about producer fields. Using a producer method to lookup a resource is totally legit:

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

public class EntityManagerProducer {

    @PersistenceContext    
    private EntityManager em;

    @Produces
    @RequestScoped
    public EntityManager getEntityManager() {
        return em;
    }
}

First, the container will instantiate the producer and a container-managed entity manager reference will be injected into the emfield. Then the container will call your producer method and wrap what he returns in a request scoped CDI proxy. This CDI proxy is what your client code get when using @Inject. Because the producer class is @Dependent (default), the underlying container-managed entity manager reference will not be shared by any other CDI proxies produced. Every time another request want the entity manager, a new instance of the producer class will be instantiated and a new entity manager reference will be injected into the producer which in turn is wrapped in a new CDI proxy.

首先,容器将实例化生产者,并将容器管理的实体管理器引用注入该em字段。然后容器将调用您的生产者方法并将他返回的内容包装在请求范围的 CDI 代理中。此 CDI 代理是您的客户端代码在使用@Inject. 因为生产者类是@Dependent(默认),底层容器管理的实体管理器引用不会被任何其他产生的 CDI 代理共享。每次另一个请求需要实体管理器时,生产者类的一个新实例将被实例化,一个新的实体管理器引用将被注入生产者,而生产者又被包装在一个新的 CDI 代理中。

To be technically correct, the underlying and unnamed container who do the resource injection into the field emis allowed to reuse old entity managers (see footnote in JPA 2.1 specification, section "7.9.1 Container Responsibilities", page 357). But so far, we honor the programming model required by JPA.

从技术上讲,将资源注入该领域的底层和未命名容器em允许重用旧的实体管理器(参见 JPA 2.1 规范中的脚注,“7.9.1 Container Responsibilities”部分,第 357 页)。但到目前为止,我们尊重 JPA 要求的编程模型。

In the preceding example, it would not matter if you mark EntityManagerProducer@Dependent or @RequestScoped. Using @Dependent is semantically more correct. But if you put a wider scope than request scope on the producer class you risk exposing the underlying entity manager reference to many threads which we both know is not a good thing to do. The underlying entity manager implementation is probably a thread-local object, but portable applications cannot rely on implementation details.

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

CDI does not know how to close whatever stuff it is that you put into the request bound context. More so than anything else, a container-managed entity manager must not be closed by application code.

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

JPA 2.1, section "7.9.1 Container Responsibilities":

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

The container must throw the IllegalStateException if the application calls EntityManager.close on a container-managed entity manager.

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

Unfortunately, many people do use a @Disposesmethod to close the container-managed entity manager. Who can blame them when the official Java EE 7 tutorialprovided by Oracle as well as the CDI specificationitself use a disposer to close a container-managed entity manager. This is simply wrong and the call to EntityManager.close()will throw a IllegalStateExceptionno matter where you put that call, in a disposer method or somewhere else. The Oracle example is the biggest sinner of the two by declaring the producer class to be a @javax.inject.Singleton. As we learned, this risk exposing the underlying entity manager reference to many different threads.

不幸的是,许多人确实使用一种@Disposes方法来关闭容器管理的实体管理器。当Oracle 提供的官方Java EE 7 教程以及CDI 规范本身使用处置器关闭容器管理的实体管理器时,谁能责怪他们。这完全是错误的,无论您将调用放在哪里,在处理程序方法中还是在其他地方,调用EntityManager.close()都会抛出一个IllegalStateException。Oracle 示例是两者中最大的罪魁祸首,将生产者类声明为@javax.inject.Singleton. 正如我们所了解到的,这种风险将底层实体管理器引用暴露给许多不同的线程。

It has been proven herethat by using CDI producers and disposers wrongfully, 1) the not thread-safe entity manager may be leaked to many threads and 2) the disposer has no effect; leaving the entity manager open. What happened was the IllegalStateException which the container swallowed leaving no trace of it (a mysterious log entry is made which says there was an "error destroying an instance").

这里已经证明,通过错误地使用 CDI 生产者和处置器,1) 非线程安全的实体管理器可能会泄漏给许多线程,2) 处置器不起作用;让实体管理器保持打开状态。发生的事情是容器吞下的 IllegalStateException 没有留下任何痕迹(创建了一个神秘的日志条目,表明存在“破坏实例的错误”)。

Generally, using CDI to lookup container-managed entity managers is not a good idea. The application is most likely better off just using @PersistenceContextand be happy with it. But there are always exceptions to the rule like in your example, and CDI can also be useful to abstract away the EntityManagerFactorywhen handling the life cycle of application-managed entity managers.

通常,使用 CDI 查找容器管理的实体管理器并不是一个好主意。该应用程序最有可能更好地使用@PersistenceContext并对其感到满意。但是像您的示例中的规则总是有例外,并且 CDI 也可以用于抽象EntityManagerFactory处理应用程序管理的实体管理器的生命周期时。

To get a complete picture on how to obtain a container-managed entity manager and how to use CDI to lookup entity managers, you might want to read thisand this.

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

回答by Groovieman

I understand your problem. but that is not a real one. Do not get messed up with the CDI declared scope of a containing class, that will propagate the scope of the attributes expect those that use @Inject'ion!

我理解你的问题。但这不是真的。不要弄乱包含类的 CDI 声明范围,这将传播属性的范围,除非使用@Inject'ion!

The @Inject'ed will compute their reference in depencency of the CDI-declaration of the implementation class. So you might have Applicationscoped class with a @Inject EntityManager em inside, but each controlflow will find its own em-transaction reference to a disjount em-object, because of the EntityManager CDI declaration of the implementation class behind.

@Inject'ed 将根据实现类的 CDI 声明来计算它们的引用。所以你可能有一个带有 @Inject EntityManager em 的 Applicationscoped 类,但是由于后面的实现类的 EntityManager CDI 声明,每个控制流都会找到它自己的对不相交的 em 对象的 em-transaction 引用。

The wrong thing of your code is, that you provide an inner getEntityManager() access method. Do not pass Injected object, if you need one, simply @Inject it .

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