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.