Obtenir une référence à EntityManager dans les applications Java EE en utilisant CDI

J'utilise Java EE 7. Je voudrais savoir quelle est la bonne façon d'injecter un JPA EntityManager dans un application étendue CDI bean. Vous ne pouvez pas simplement l'injecter en utilisant @PersistanceContext annotation, car EntityManager les instances ne sont pas sécurisées. Supposons que nous voulons que notre EntityManager doit être 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éer une demande portée CDI bean qui a une référence à un EntityManager puis injecter la fève dans une 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);
    }
}

Dans cet exemple, un EntityManager sera créé lorsque l' RequestScopedBean est créé, et sera fermé lorsque l' RequestScopedBean est détruit. Maintenant je peux déplacer l'injection dans une classe abstraite pour l'enlever de la ApplicationScopedBean.

2. Créer un producteur qui produit des instances de EntityManager, puis injectez le EntityManager exemple dans une application l'étendue bean CDI.

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 aussi un EntityManager qui est créé pour chaque requête HTTP, mais qu'en est-il de clôture<!--4? Sera-t-il également fermé après le traitement de la requête HTTP? Je sais que l' @PersistanceContext annotation injecte container-managed EntityManager. Cela signifie qu'un EntityManager sera fermé lorsqu'un haricot client est détruit. Qu'est ce qu'un client bean dans cette situation? Est-ce l' ApplicationScopedBean, qui ne sera jamais être détruite jusqu'à ce que l'application s'arrête, ou est-ce l' EntityManagerProducer? Des conseils?

je sais que je pourrais utiliser un EJB stateless lieu de l'application étendue de haricot et puis juste injecter EntityManager par @PersistanceContext annotation, mais ce n'est pas la question :)

20
demandé sur Martin Andersson 2013-10-17 19:49:33

4 réponses

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 comme conteneur CDI (GlassFish 4.1 et WildFly 8.2.0), alors votre exemple de combinaison @Produces,@PersistenceContext et @RequestScoped sur un producteur champ devrait lever cette exception au cours du déploiement:

org.jboss.souder.exception.DefinitionException: WELD-001502: Champ producteur de ressources [Champ producteur de ressources [EntityManager] avec les qualificatifs [@Tous @Default] a déclaré que [[BackedAnnotatedField] @Produits @RequestScoped @PersistenceContext COM.somepackage.EntityManagerProducer.entityManager]] doit être @Dépendant de l'étendue

il S'avère que le conteneur n'est pas nécessaire de supporter une autre Portée que @Dependent lors de l'utilisation d'un champ producer pour rechercher des ressources Java EE.

CDI 1.2, section 3.7. Ressources:

le conteneur n'est pas nécessaire pour soutenir les ressources avec la portée autre que @Dépendante. Les applications portables ne devraient pas définir les ressources avec toute autre étendue que @Dépendante.

cette citation concernait uniquement les champs des producteurs. Utiliser la méthode du producteur pour rechercher une ressource est tout à fait légal:

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é gérée par conteneur sera injectée dans le em champ. Puis le conteneur appellera votre méthode producer et enveloppera ce qu'il retourne dans une requête mandatée par le CDI. Ce mandataire CDI est ce que votre code client obtient en utilisant @Inject. Étant donné que la catégorie de producteurs est @dépendante (défaut), la référence sous-jacente de gestionnaire d'entité gérée par conteneur ne sera pas partagée par d'autres mandataires du CDI produits. Chaque fois qu'une autre requête veut le gestionnaire d'entity, une nouvelle instance de la classe producer sera instanciée et une nouvelle référence de gestionnaire d'entity sera injectée dans le producteur qui, à son tour, est enveloppé dans un nouveau proxy CDI.

pour être techniquement correct, le conteneur sous-jacent et sans nom qui fait l'injection de la ressource dans le champ em est autorisé à réutiliser les anciens gestionnaires d'entités (Voir note de bas de page dans la spécification JPA 2.1, section "7.9.1 responsabilités relatives aux conteneurs", page 357). Mais jusqu'à présent, nous honorons le modèle de programmation requis par JPA.

Dans l'exemple précédent, il n'aurait pas d'importance si vous marquez EntityManagerProducer @Dépendantes ou @RequestScoped. Utiliser @Dependent est sémantiquement plus correct. Mais si vous mettez une portée plus large que request scope sur la classe producer, vous risquez d'exposer la référence du gestionnaire d'entité sous-jacent à de nombreux threads qui, nous le savons tous les deux, n'est pas une bonne chose à faire. L'implémentation de entity manager sous-jacente est probablement un objet thread-local, mais les applications portables ne peuvent pas se fier aux détails de l'implémentation.

le CDI ne sait pas comment fermer quelque chose c'est que vous mettez dans la demande lié au contexte. Plus que toute autre chose, un gestionnaire d'entité géré par conteneur ne doit pas être fermé par un code d'application.

JPA 2.1, section "7.9.1 responsabilités relatives aux conteneurs":

le conteneur doit jeter L'Exception IllegalStateException si l'application calls EntityManager.fermez un gestionnaire d'entité de gestion de conteneurs.

Malheureusement, beaucoup de gens utilisent un @Disposes méthode de fermeture du gestionnaire d'entités géré par conteneur. Qui peut les blâmer quand l'officiel Java EE 7 tutorial fourni par Oracle ainsi que l' spécification CDI elle-même utiliser un éliminateur pour fermer un gestionnaire d'entité à gestion de conteneurs. C'est tout simplement faux et l'appel à EntityManager.close() lance IllegalStateException peu importe où vous mettez cet appel, dans une méthode d'élimination ou ailleurs. L'exemple D'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 d'exposer le sous-jacent entity manager référence à de nombreux threads différents.

Il a été prouvé ici qu'en utilisant les producteurs et les éliminateurs du CDI de manière erronée, 1) le gestionnaire d'entité non sécurisé peut être divulgué à de nombreux threads et 2) l'éliminateur n'a aucun effet; laissant le gestionnaire d'entité ouvert. Ce qui s'est passé, C'est L'Exception illégale que le conteneur a avalé sans laisser de trace (une inscription mystérieuse dans un journal indique qu'il y a eu une " erreur détruisant un instance.)"

en général, l'utilisation du CDI pour rechercher les gestionnaires d'entités gérées par conteneurs n'est pas une bonne idée. L'application est probablement préférable d'utiliser simplement @PersistenceContext et être heureux avec elle. Mais il y a toujours des exceptions à la règle comme dans votre exemple, et le CDI peut aussi être utile pour faire abstraction de la EntityManagerFactory lors de la gestion du cycle de vie des gestionnaires d'entités gérés par applications.

pour obtenir une image complète sur la façon d'obtenir un gestionnaire d'entité de gestion de conteneur et comment pour utiliser le CDI pour rechercher des gestionnaires d'entités, vous pouvez lire et .

28
répondu Martin Andersson 2016-05-27 03:37:05

je comprends votre problème. mais ce n'est pas un vrai. Ne pas se tromper avec le CDI déclaré scope d'une classe containing, qui propagera la portée des attributs s'attend à ceux qui utilisent @Injec'ion!

les @Inject'EED calculeront leur référence en fonction de la déclaration CDI de la classe de mise en oeuvre. Vous pouvez donc avoir la classe Applicationscoped avec un em @Inject EntityManager à l'intérieur, mais chaque flux de contrôle trouvera sa propre référence em-transaction à un disjount em-object, en raison de la déclaration EntityManager CDI de la classe de mise en œuvre derrière.

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

1
répondu Groovieman 2018-06-15 10:28:27

Vous devez utiliser le @Dispose annotation pour fermer le 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
répondu Claudio Miranda 2016-06-28 21:26:17

Vous pouvez injecter savely Entitmanagerfactory, c'est thread save

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

alors vous pouvez récupérer EntityManager de entityManagerFactory.

-1
répondu Piotr Kochański 2013-10-17 16:01:28