且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-12-03 23:49:04

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



如果你使用Weld作为CDI容器(GlassFish 4.1和WildFly 8.2.0),然后组合 @Produces @PersistenceContext @RequestScoped $ c $的例子c>在生产者字段上应该在部署期间抛出此异常:


org.jboss.weld.exceptions。 DefinitionException:WELD-001502:
资源生成器字段[Resource Producer Field [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
私有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 $则无关紧要c $ c> @Dependent或@RequestScoped。使用@Dependent在语义上更正确。但是如果你在生产者类上放置比请求范围更宽的范围,那么冒险将底层实体管理器引用暴露给许多线程,我们都知道这些线程并不是一件好事。底层实体管理器实现可能是一个线程本地对象,但是便携式应用程序不能依赖于实现细节。



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



JPA 2.1,7.9.1容器责任部分:


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


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



已经证明在其中错误地使用CDI生产商和处置者, 1)非线程安全的实体管理器可能泄露给许多线程,2)处理器没有效果;让实体经理开放。发生了什么是容器吞没的IllegalStateException,没有留下任何痕迹(一个神秘的日志条目,说有一个错误破坏一个实例)。



一般来说,使用CDI查找容器管理的实体管理器并不是一个好主意。只需使用 @PersistenceContext ,应用程序就更好了,并对此感到满意。但是,在您的示例中,规则总是存在例外情况,并且在处理应用程序管理的实体管理器的生命周期时,CDI对于抽象出 EntityManagerFactory 也很有用。 / p>

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


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

1. Create a request scoped CDI bean which has a reference to an EntityManager and then inject the bean into an application scoped 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 EntityManager will be created when the RequestScopedBean is created, and will be closed when the RequestScopedBean is destroyed. Now I could move the injection to some abstract class to remove it from the ApplicationScopedBean.

2. Create a producer that produces instances of EntityManager, and then inject the EntityManager instance into an application scoped 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 EntityManager which is created every HTTP request, but what about closing the EntityManager? Will it also be closed after the HTTP request is processed? I know that the @PersistanceContext annotation injects container-managed EntityManager. This means that an EntityManager will 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?

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

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

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

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

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.

CDI 1.2, section 3.7. Resources:

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.

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 em field. 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.

To be technically correct, the underlying and unnamed container who do the resource injection into the field em is 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.

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.

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.

JPA 2.1, section "7.9.1 Container Responsibilities":

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

Unfortunately, many people do use a @Disposes method to close the container-managed entity manager. Who can blame them when the official Java EE 7 tutorial provided by Oracle as well as the CDI specification itself use a disposer to close a container-managed entity manager. This is simply wrong and the call to EntityManager.close() will throw a IllegalStateException no 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.

It has been proven here that 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").

Generally, using CDI to lookup container-managed entity managers is not a good idea. The application is most likely better off just using @PersistenceContext and 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 EntityManagerFactory when handling the life cycle of application-managed entity managers.

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 this and this.