web-dev-qa-db-fra.com

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

J'utilise Java EE 7. J'aimerais savoir quelle est la bonne méthode pour injecter un JPA EntityManager dans un bean CDI application couvert. Vous ne pouvez pas simplement l'injecter en utilisant l'annotation @PersistanceContext, car les instances de EntityManager ne sont pas thread-safe. Supposons que nous voulions que notre EntityManager soit créée au début de chaque traitement de requête HTTP et fermée après le traitement de la requête HTTP. Deux options me viennent à l’esprit:

1 . Créez un bean CDI délimité par la requête avec une référence à une variable EntityManager, puis injectez-le dans un bean CDI délimité par une 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, une EntityManager sera créée lors de la création de la RequestScopedBean et sera fermée lors de la destruction de la RequestScopedBean. Je pourrais maintenant déplacer l'injection dans une classe abstraite pour la supprimer de la ApplicationScopedBean.

2 . Créez un producteur qui produit des instances de EntityManager, puis injectez l'instance EntityManager dans un bean CDI couvert par l'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 une EntityManager qui est créée à chaque demande HTTP, mais qu'en est-il de fermer la EntityManager? Sera-t-il également fermé après le traitement de la demande HTTP? Je sais que l'annotation @PersistanceContext injecte la variable EntityManager gérée par le conteneur. Cela signifie qu'une EntityManager sera fermée lorsqu'un bean client est détruit. Qu'est-ce qu'un haricot client dans cette situation? Est-ce la ApplicationScopedBean, qui ne sera jamais détruite jusqu'à ce que l'application s'arrête, ou est-ce la EntityManagerProducer? Des conseils?

Je sais que je pourrais utiliser un EJB sans état au lieu d'un bean d'application, puis injecter l'annotation EntityManager by @PersistanceContext, mais ce n'est pas le problème :)

20
Flying Dumpling

Vous avez presque raison avec votre producteur CDI. La seule chose est que vous devriez utiliser une méthode de producteur au lieu d'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 producteur champ doit renvoyer cette exception lors du déploiement:

org.jboss.weld.exceptions.DefinitionException: WELD-001502: Champ producteur de ressources [Champ producteur de ressources [EntityManager] avec qualificateurs [@Any @Default] déclarés comme [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer.entityManager]] doit être @Dépendants

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 les ressources avec une autre portée. que @Dependent. Les applications portables ne doivent pas définir de ressources avec une portée autre que @Dependent.

Cette citation était tout au sujet des champs de producteur. 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 votre code client obtient lorsque vous utilisez @Inject. Étant donné que la classe de producteur est @Dependent (par défaut), la référence de gestionnaire d'entités gérée par conteneur sous-jacente 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 sera instanciée et une nouvelle référence de gestionnaire d'entités sera injectée dans le producteur, qui sera à son tour encapsulée dans un nouveau proxy CDI.

Pour être techniquement correct, le conteneur sous-jacent et non nommé qui injecte la ressource 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 requis par JPA.

Dans l'exemple précédent, peu importe si vous cochez EntityManagerProducer @Dependent ou @RequestScoped. Utiliser @Dependent est sémantiquement plus correct. Mais si vous définissez une portée plus large que la portée de la demande sur la catégorie de producteurs, vous risquez d'exposer la référence du responsable de l'entité sous-jacente à de nombreux fils qui, nous le savons tous les deux, ne sont pas une bonne chose à faire. L'implémentation du gestionnaire d'entités sous-jacent est probablement un objet local au thread, mais les applications portables ne peuvent pas compter sur des détails d'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 du conteneur":

Le conteneur doit lancer l'exception IllegalStateException si l'application appelle EntityManager.close sur un gestionnaire d'entités géré 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 en blâmer lorsque le tutoriel Java EE 7 officiel fourni par Oracle ainsi que la spécification CDI utilise lui-même un destructeur pour fermer un gestionnaire d'entités gérées par conteneur. C’est tout simplement faux et l’appel à EntityManager.close() jettera un IllegalStateException peu importe où vous avez passé cet appel, dans une méthode de passeur 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 thread-safe peut subir des fuites sur de nombreux threads et 2) l'éliminateur n'a aucun effet. laissant le gestionnaire d'entité ouvert. Il s’agit de l’exception IllegalStateException que le conteneur a avalée sans en laisser aucune trace (une entrée mystérieuse dans le journal 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’application simplement @PersistenceContext et d’en être satisfaite. Mais il existe toujours des exceptions à la règle, comme dans votre exemple, et CDI peut également être utile pour extraire la variable EntityManagerFactory lors du traitement du cycle de vie des gestionnaires d'entités gérées par l'application.

Pour obtenir une image complète de la procédure d'obtention d'un gestionnaire d'entités géré par conteneur et de l'utilisation de CDI pour rechercher des gestionnaires d'entités, vous pouvez lire this et this .

31

Je comprends ton problème. mais ce n'est pas un vrai. Ne vous fâchez pas avec la portée déclarée par le CDI d'une classe contenante, cela propagera la portée des attributs, sauf 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. Vous pouvez donc avoir la classe Applicationscoped avec un @Inject EntityManager em à l'intérieur, mais chaque flux de contrôle trouvera sa propre référence em-transaction à un e-objet disjount, en raison de la déclaration EntityManager CDI de la classe d'implémentation située derrière.

La mauvaise chose dans votre code est que vous fournissiez une méthode d'accès getEntityManager () interne. Ne passez pas l'objet injecté, si vous en avez besoin, il suffit de @ l'injecter. 

1
Groovieman

Vous devez utiliser l'annotation @Dispose pour fermer la 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();
        }
    }

}
0
Claudio Miranda