Obtention d’une référence à EntityManager dans les applications Java EE à l’aide de CDI

J’utilise Java EE 7. Je voudrais savoir comment injecter un JPA EntityManager dans un bean CDI d’une scope d’application . Vous ne pouvez pas simplement l’injecter en utilisant l’annotation @PersistanceContext , car les instances de @PersistanceContext ne sont pas thread-safe. Supposons que nous voulons que notre EntityManager soit créé au début de chaque traitement de requête HTTP et fermé après le traitement de la requête HTTP. Deux options me viennent à l’esprit:

1. Créez un bean CDI d’une requête ayant une référence à un EntityManager , puis injectez le bean dans un bean CDI de scope d’application.

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

Dans cet exemple, EntityManager sera créé lors de la création de RequestScopedBean et sera fermé lorsque RequestScopedBean sera détruit. Je peux maintenant déplacer l’injection dans une classe abstraite pour la supprimer de ApplicationScopedBean .

2. Créez un producteur qui produit des instances de EntityManager , puis injectez l’instance EntityManager dans un bean CDI de scope d’application.

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

Dans cet exemple, nous aurons également un EntityManager qui crée chaque requête HTTP, mais qu’en est-il de la fermeture de EntityManager ? Sera-t-il également fermé après le traitement de la requête HTTP? Je sais que l’annotation @PersistanceContext injecte @PersistanceContext géré par le conteneur. Cela signifie qu’un EntityManager sera fermé lorsqu’un bean client est détruit. Qu’est-ce qu’un haricot client dans cette situation? Est-ce l’ ApplicationScopedBean , qui ne sera jamais détruite tant que l’application ne sera pas arrêtée ou est-ce que c’est EntityManagerProducer ? Des conseils?

Je sais que je pourrais utiliser un EJB sans état au lieu d’un bean d’application, puis injecter @PersistanceContext par l’annotation @PersistanceContext , mais ce n’est pas le @PersistanceContext 🙂

Vous avez presque raison avec votre producteur de CDI. La seule chose à faire est d’utiliser une méthode de producteur plutôt qu’un champ de producteur.

Si vous utilisez Weld en tant que conteneur CDI (GlassFish 4.1 et WildFly 8.2.0), votre exemple de combinaison de @Produces , @PersistenceContext et @RequestScoped sur un champ de producteur devrait @RequestScoped cette exception lors du déploiement:

org.jboss.weld.exceptions.DefinitionException: WELD-001502: Champ producteur de ressources [Champ producteur de ressources [EntityManager] avec les qualificatifs [@Any @Default] déclarés comme [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext.PossistenceContext. entityManager]] doit être @Dépendant

Il s’avère que le conteneur n’est pas obligé de prendre en charge une autre étendue que @Dependent lors de l’utilisation d’un champ producteur pour rechercher des ressources Java EE.

CDI 1.2, section 3.7. Ressources:

Le conteneur n’est pas obligé de prendre en charge des ressources avec une étendue autre que @Dependent. Les applications portables ne doivent pas définir de ressources avec une étendue autre que @Dependent.

Cette citation portait sur les champs de production. Utiliser une méthode de producteur pour rechercher une ressource est totalement légitime:

 public class EntityManagerProducer { @PersistenceContext private EntityManager em; @Produces @RequestScoped public EntityManager getEntityManager() { return em; } } 

Tout d’abord, le conteneur instanciera le producteur et une référence de gestionnaire d’entités gérée par conteneur sera injectée dans le champ em . Ensuite, le conteneur appellera votre méthode de producteur et encapsulera ce qu’il retourne dans un proxy CDI couvert par une requête. Ce proxy CDI correspond à ce que reçoit votre code client lors de l’utilisation de @Inject . Étant donné que la classe de production est @Dependent (valeur par défaut), la référence du gestionnaire d’entités gérées par le conteneur sous-jacent ne sera partagée par aucun autre proxy CDI produit. Chaque fois qu’une autre requête souhaite le gestionnaire d’entités, une nouvelle instance de la classe de producteurs est instanciée et une nouvelle référence de gestionnaire d’entités est injectée dans le producteur, qui est à son tour encapsulée dans un nouveau proxy CDI.

Pour être techniquement correct, le conteneur sous-jacent et non nommé qui injecte les ressources dans le champ em est autorisé à réutiliser les anciens gestionnaires d’entités (voir la note de bas de page de la spécification JPA 2.1, section “7.9.1 Responsabilités du conteneur”, page 357). Mais jusqu’à présent, nous respectons le modèle de programmation requirejs par JPA.

Dans l’exemple précédent, cela n’aurait pas d’importance si vous marquez EntityManagerProducer @Dependent ou @RequestScoped. Utiliser @Dependent est sémantiquement plus correct. Mais si vous placez une scope plus large que la scope de la requête sur la classe de producteur, vous risquez d’exposer la référence du gestionnaire d’entité sous-jacente à de nombreux threads, ce que nous soaps tous deux n’est pas une bonne chose à faire. L’implémentation du gestionnaire d’entités sous-jacent est probablement un object local au thread, mais les applications portables ne peuvent pas compter sur les détails de l’implémentation.

CDI ne sait pas comment fermer le contenu que vous avez mis dans le contexte lié à la demande. Plus que toute autre chose, un gestionnaire d’entités géré par conteneur ne doit pas être fermé par un code d’application.

JPA 2.1, section “7.9.1 Responsabilités des conteneurs”:

Le conteneur doit lancer l’exception IllegalStateException si l’application appelle EntityManager.close sur un gestionnaire d’entités gérées par conteneur.

Malheureusement, de nombreuses personnes utilisent une méthode @Disposes pour fermer le gestionnaire d’entités gérées par le conteneur. Qui peut les blâmer lorsque le tutoriel officiel Java EE 7 fourni par Oracle, ainsi que la spécification CDI elle-même, utilisent un broyeur pour fermer un gestionnaire d’entités géré par conteneur. C’est tout simplement faux et l’appel à EntityManager.close() lancera une IllegalStateException peu importe où vous placez cet appel, dans une méthode de suppression ou ailleurs. L’exemple Oracle est le plus grand pécheur des deux en déclarant que la classe de producteur est un @javax.inject.Singleton . Comme nous l’avons appris, ce risque expose la référence du gestionnaire d’entité sous-jacente à de nombreux threads différents.

Il a été prouvé ici qu’en utilisant à tort les producteurs et les éliminateurs de CDI, 1) le gestionnaire d’entités non sûres peut être divulgué sur de nombreux threads et 2) le broyeur n’a aucun effet; laissant le gestionnaire d’entité ouvert. Ce qui s’est passé, c’est le IllegalStateException que le conteneur a avalé sans laisser de trace (une entrée de journal mystérieuse indique qu’il y avait une “erreur lors de la destruction d’une instance”).

En règle générale, utiliser CDI pour rechercher des gestionnaires d’entités gérés par conteneur n’est pas une bonne idée. Il est probablement préférable d’utiliser l’ @PersistenceContext simplement @PersistenceContext et d’en être satisfaite. Mais il y a toujours des exceptions à la règle comme dans votre exemple, et CDI peut également être utile pour séparer EntityManagerFactory lors de la gestion du cycle de vie des gestionnaires d’entités gérées par l’application.

Pour obtenir une image complète de la façon d’obtenir un gestionnaire d’entités gérées par conteneur et d’utiliser CDI pour rechercher des gestionnaires d’entité, vous pouvez lire ceci et cela .

Je comprends votre problème mais ce n’est pas réel. Ne vous trompez pas avec la scope déclarée par CDI d’une classe contenant, qui propagera la scope des atsortingbuts attendus par ceux qui utilisent @ Inject’ion!

Le @ Inject’ed calculera sa référence en dépendance de la déclaration CDI de la classe de mise en oeuvre. Ainsi, vous pourriez avoir une classe Applicationscoped avec un @Inject EntityManager em à l’intérieur, mais chaque stream de contrôle trouvera sa propre référence em-transaction à un e-object disjount, en raison de la déclaration EntityManager CDI de la classe d’implémentation se trouvant derrière.

La mauvaise chose dans votre code est que vous fournissiez une méthode d’access getEntityManager () interne. Ne passez pas l’object Injected, si vous en avez besoin, il suffit de @Injecter-le.

Vous devez utiliser l’annotation @Dispose pour fermer EntityManager , comme dans l’exemple ci-dessous:

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

Vous pouvez injecter savamment EntityManagerFactory, il s’agit de thread save

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

alors vous pouvez ré-entrer EntityManager à partir de entityManagerFactory.