que - Obtener una referencia a EntityManager en aplicaciones Java EE usando CDI
jta java (3)
Estoy utilizando Java EE 7. Me gustaría saber cuál es la forma correcta de inyectar un JPA EntityManager
en un bean CDI con ámbito de aplicación . No puede simplemente inyectarlo usando la anotación @PersistanceContext
, porque EntityManager
instancias de EntityManager
no son seguras para hilos. Supongamos que queremos que nuestro EntityManager
se cree al comienzo de cada procesamiento de solicitud HTTP y se cierre después de que se procese la solicitud HTTP. Dos opciones vienen a mi mente:
1. Cree un bean CDI con ámbito de solicitud que tenga una referencia a EntityManager
y luego inyecte el bean en un bean CDI con ámbito de aplicación.
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);
}
}
En este ejemplo, se EntityManager
un EntityManager
cuando se RequestScopedBean
el RequestScopedBean
, y se cerrará cuando se destruya RequestScopedBean
. Ahora podría mover la inyección a alguna clase abstracta para eliminarla de ApplicationScopedBean
.
2. Cree un productor que produzca instancias de EntityManager
y luego EntityManager
instancia de EntityManager
en un bean CDI con ámbito de aplicación.
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);
}
}
En este ejemplo, también tendremos un EntityManager
que se crea cada solicitud HTTP, pero ¿qué pasa con el cierre de EntityManager
? ¿Se cerrará también después de procesar la solicitud HTTP? Sé que la anotación @PersistanceContext
inyecta EntityManager
administrado por EntityManager
. Esto significa que un EntityManager
se cerrará cuando se destruya un bean de cliente. ¿Qué es un cliente Bean en esta situación? ¿Es el ApplicationScopedBean
, que nunca se destruirá hasta que se detenga la aplicación, o es el EntityManagerProducer
? ¿Algún consejo?
Sé que podría usar un EJB sin estado en lugar de un bean con ámbito de aplicación y luego solo EntityManager
mediante la anotación @PersistanceContext
, pero ese no es el punto :)
Puede inyectar savely EntityManagerFactory, es guardar el hilo
@PersistenceUnit(unitName = "myUnit")
private EntityManagerFactory entityManagerFactory;
luego puede recuperar EntityManager de entityManagerFactory.
Estás casi en lo cierto con tu productor de CDI. Lo único es que debe usar un método de productor en lugar de un campo de productor.
Si está utilizando Weld como contenedor CDI (GlassFish 4.1 y WildFly 8.2.0), su ejemplo de combinación de @Produces
, @PersistenceContext
y @RequestScoped
en un campo de productor debería arrojar esta excepción durante la implementación:
org.jboss.weld.exceptions.DefinitionException: WELD-001502: Campo del productor de recursos [Resource Producer Field [EntityManager] con calificadores [@Any @Default] declarado como [[BackedAnnotetedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer. entityManager]] debe tener el alcance @Dependent
Resulta que no se requiere que el contenedor soporte ningún otro alcance que @Dependent cuando se usa un campo de productor para buscar recursos de Java EE.
CDI 1.2, sección 3.7. Recursos:
El contenedor no es necesario para admitir recursos con un alcance que no sea @Dependent. Las aplicaciones portátiles no deben definir recursos con un alcance que no sea @Dependent.
Esta cita fue sobre campos de productores. Usar un método de productor para buscar un recurso es totalmente legítimo:
public class EntityManagerProducer {
@PersistenceContext
private EntityManager em;
@Produces
@RequestScoped
public EntityManager getEntityManager() {
return em;
}
}
En primer lugar, el contenedor creará una instancia del productor y se inyectará una referencia de administrador de entidad gestionada por contenedor en el campo em
. Luego, el contenedor llamará a su método de productor y ajustará lo que devuelve en un proxy de CDI con ámbito de solicitud. Este proxy CDI es lo que obtiene su código de cliente cuando usa @Inject
. Debido a que la clase de productor es @Dependent (predeterminado), la referencia del administrador de entidad administrada por contenedor subyacente no será compartida por ningún otro proxy CDI producido. Cada vez que otra solicitud solicite el administrador de entidades, se instanciará una nueva instancia de la clase de productores y se inyectará una nueva referencia de administrador de entidades en el productor, que a su vez se envuelve en un nuevo proxy CDI.
Para ser técnicamente correcto, el contenedor subyacente y sin nombre que realiza la inyección de recursos en el campo em
puede reutilizar los antiguos administradores de entidades (consulte la nota al pie en la especificación JPA 2.1, sección "7.9.1 Responsabilidades del contenedor", página 357). Pero hasta ahora, respetamos el modelo de programación requerido por JPA.
En el ejemplo anterior, no importaría si marcas EntityManagerProducer
@Dependent o @RequestScoped. Usar @Dependent es semánticamente más correcto. Pero si se pone un alcance más amplio que el alcance de la solicitud en la clase de productor, se corre el riesgo de exponer la referencia del administrador de entidades subyacente a muchos hilos que ambos sabemos que no son algo bueno. La implementación del gestor de entidades subyacente es probablemente un objeto local de subprocesos, pero las aplicaciones portátiles no pueden confiar en los detalles de implementación.
CDI no sabe cómo cerrar lo que sea que coloque en el contexto vinculado a la solicitud. Más que cualquier otra cosa, un administrador de la entidad administrado por contenedor no debe estar cerrado por el código de la aplicación.
JPA 2.1, sección "7.9.1 Responsabilidades del contenedor":
El contenedor debe lanzar IllegalStateException si la aplicación llama a EntityManager.close en un administrador de entidades gestionado por contenedor.
Desafortunadamente, muchas personas usan un método @Disposes
para cerrar el administrador de entidades administrado por contenedor. ¿Quién puede culparlos cuando el tutorial oficial de Java EE 7 provisto por Oracle, así como la especificación CDI en sí, usan un triturador para cerrar un administrador de entidades gestionado por contenedores? Esto es simplemente incorrecto y la llamada a EntityManager.close()
arrojará una IllegalStateException
sin importar dónde ponga esa llamada, en un método de triturador o en otro lugar. El ejemplo de Oracle es el pecador más grande de los dos al declarar que la clase de productor es un @javax.inject.Singleton
. Como aprendimos, este riesgo expone la referencia del administrador de entidad subyacente a muchos hilos diferentes.
Se ha demostrado aquí que al usar generadores y trituradores de CDI erróneamente, 1) el administrador de entidades no seguro para subprocesos puede filtrarse a muchos hilos y 2) el triturador no tiene ningún efecto; dejando abierto el administrador de la entidad Lo que sucedió fue la IllegalStateException que el contenedor se tragó sin dejar rastro de ella (se realiza una misteriosa entrada de registro que dice que hubo un "error que destruyó una instancia").
En general, usar CDI para buscar administradores de entidades administrados por contenedores no es una buena idea. La aplicación probablemente esté mejor utilizando solo @PersistenceContext
y esté contento con ella. Pero siempre hay excepciones a la regla como en su ejemplo, y el CDI también puede ser útil para abstraer el EntityManagerFactory
al manejar el ciclo de vida de los administradores de entidades administradas por la aplicación.
Para obtener una imagen completa de cómo obtener un administrador de entidades gestionado por contenedor y cómo usar CDI para buscar administradores de entidades, es posible que desee leer esto y esto .
Debe usar la anotación @Dispose
para cerrar EntityManager
, como en el ejemplo siguiente:
@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();
}
}
}