2013-10-17 20 views
18

Ich benutze Java EE 7. Ich würde gerne wissen, was der richtige Weg ist, eine JPA EntityManager in eine Anwendung Bereich CDI Bean zu injizieren. Sie können es nicht einfach unter Verwendung der @PersistanceContext Annotation injizieren, da EntityManager Instanzen nicht threadsicher sind. Nehmen wir an, wir möchten, dass unsere EntityManager am Anfang jeder HTTP-Anfrageverarbeitung erstellt und geschlossen wird, nachdem die HTTP-Anfrage verarbeitet wurde. Zwei Möglichkeiten kommen mir in den Sinn:Einen Verweis auf EntityManager in Java EE-Anwendungen mit CDI erhalten

1. Erstellen Sie eine Anfrage bereich CDI Bean, die eine Referenz auf eine EntityManager hat und dann injizieren Sie die Bohne in einer Anwendung Bereich 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); 
    } 
} 

In diesem Beispiel wird ein EntityManager wird, wenn die RequestScopedBean erstellt wird erstellt werden, und wird geschlossen, wenn die RequestScopedBean zerstört wird. Jetzt könnte ich die Injektion in eine abstrakte Klasse verschieben, um sie aus der ApplicationScopedBean zu entfernen.

2. Erstellen eines Produzenten, die Instanzen von EntityManager, und dann injizieren die EntityManager Instanz in eine Anwendung scoped CDI Bean erzeugt.

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

In diesem Beispiel werden wir auch eine EntityManager haben, die jede HTTP-Anforderung erstellt wird, aber was ist die EntityManager schließen? Wird es auch nach der Verarbeitung der HTTP-Anfrage geschlossen? Ich weiß, dass die @PersistanceContext Annotation container-verwaltete EntityManager injiziert. Das bedeutet, dass ein EntityManager geschlossen wird, wenn eine Client-Bean zerstört wird. Was ist eine Client-Bean in dieser Situation? Ist es die ApplicationScopedBean, die nie zerstört wird, bis die Anwendung stoppt, oder ist es die EntityManagerProducer? Irgendwelche Ratschläge?

Ich weiß, dass ich ein stateless EJB verwenden könnte statt Anwendung Bohne scoped und dann injizieren nur EntityManager durch @PersistanceContext Anmerkung, aber das ist nicht der Punkt :)

Antwort

-1

Sie können savely EntityManagerFactory injizieren, ist es Thread speichern

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

Dann können Sie EntityManager von entityManagerFactory abrufen.

+0

Ich kann das tun und dann 'EntityManager' von der Fabrik in jeder Methode der Anwendung beanspruchte Bean. Aber wird es ein container-verwalteter "EntityManager" sein? Ich denke nicht. Ich möchte den Lebenszyklus eines 'EntityManagers' und Transaktionen nicht selbst verwalten. –

+0

@FlyingDumpling Welche Version von Java EE verwenden Sie? Wenn Sie vor Java EE 7 arbeiten, haben Sie ohnehin keinen Transaktions-Support in CDI-Beans. Siehe diese Antwort: http://stackoverflow.com/questions/17838221/jee7-do-ejb-and-cdi-beans-support-container-managed-transactions –

+0

@AndreiI Sorry, ich habe nicht erwähnt, verwende ich Java EE 7 –

0

Sie sollen die @Dispose Anmerkung verwenden, um die EntityManager, wie im Beispiel zu schließen unten:

@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

Sie erstellen einen anwendungsverwalteten Entitätsmanager (der durch den Anwendungscode geschlossen werden muss). Die Frage bezog sich auf containerverwaltete Entity Manager. –

25

Sie sind fast direkt mit Ihren CDI Produzenten. Sie sollten nur eine Producer-Methode anstelle eines Producer-Feldes verwenden.

Wenn Sie Weld als CDI-Container verwenden (GlassFish 4.1 und WildFly 8.2.0), dann ist dein Beispiel @Produces zu kombinieren, @PersistenceContext und @RequestScoped auf einem Hersteller Feld diese Ausnahme während des Einsatzes werfen soll:

org.jboss.weld.exceptions.DefinitionException: WELD-001502: Ressourcen Produzent Feld [Ressourcen Producer Feld [EntityManager] mit Qualifikation [@Any @Default] erklärt, wie [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer.entityManager]] muss @Dependent sein

scoped

Es stellt sich heraus, dass der Container keinen anderen Bereich als @Dependent unterstützen muss, wenn ein Herstellerfeld zum Nachschlagen von Java EE-Ressourcen verwendet wird.

CDI 1.2, Abschnitt 3.7. Ressourcen:

Der Container ist nicht erforderlich, um Ressourcen mit anderen Bereich als @ Dependent zu unterstützen. Portable Anwendungen sollten keine Ressourcen mit einem anderen Bereich als @Dependent definieren.

Dieses Zitat war alles über Erzeugerfelder. ein Produzent Methode ist eine Ressource zum Nachschlagen völlig legitim:

public class EntityManagerProducer { 

    @PersistenceContext  
    private EntityManager em; 

    @Produces 
    @RequestScoped 
    public EntityManager getEntityManager() { 
     return em; 
    } 
} 

Zunächst wird der Behälter an den Hersteller und einen Container verwaltete Einheit Manager Referenz in das Feld em injiziert wird instanziiert. Dann ruft der Container Ihre Producer-Methode auf und wickelt die von ihm zurückgegebenen Daten in einen CDI-Proxy für eine Anfrage um. Dieser CDI-Proxy ist, was Ihr Client-Code erhält, wenn Sie @Inject verwenden. Da die Producer-Klasse @Dependent ist (Standard), wird die zugrunde liegende, vom Container verwaltete Entity Manager-Referenz von keinem anderen CDI-Proxies gemeinsam genutzt. Jedes Mal, wenn eine weitere Anfrage den Entity Manager benötigt, wird eine neue Instanz der Producer-Klasse instanziiert und eine neue Entity Manager-Referenz wird in den Producer injiziert, der wiederum in einen neuen CDI-Proxy eingebunden wird.

Um technisch korrekt zu sein, darf der zugrunde liegende und unbenannte Container die Ressourceninjektion in das Feld em alte Entity-Manager wiederverwenden (siehe Fußnote in JPA 2.1-Spezifikation, Abschnitt "7.9.1 Container-Verantwortlichkeiten", Seite 357) . Aber bis jetzt respektieren wir das von JPA geforderte Programmiermodell.

Im vorherigen Beispiel wäre es egal, ob Sie EntityManagerProducer @Dependent oder @RequestScoped markieren. Die Verwendung von @Dependent ist semantisch korrekter. Wenn Sie jedoch einen breiteren Geltungsbereich als den Anforderungsbereich für die Producer-Klasse festlegen, riskieren Sie, dass der zugrunde liegende Entity-Manager-Verweis auf viele Threads freigelegt wird, von denen wir beide wissen, dass sie nicht gut sind. Die zugrunde liegende Entity Manager-Implementierung ist wahrscheinlich ein Thread-lokales Objekt, aber portable Anwendungen können sich nicht auf Implementierungsdetails verlassen.

CDI weiß nicht, wie man alles schließt, was man in den anforderungsgebundenen Kontext einfügt. Mehr als alles andere darf ein containerverwalteter Entity Manager nicht durch Anwendungscode geschlossen werden.

JPA 2.1, Abschnitt "7.9.1 Container Verantwortlichkeit":

Der Behälter mit der Illegal, wenn die Anwendung werfen muss EntityManager.close Entity Manager auf einem Container-verwaltete nennt.

Leider verwenden viele Leute eine @Disposes Methode, um den Container-verwalteten Entity Manager zu schließen. Wer kann es ihnen verdenken, wenn der von Oracle zur Verfügung gestellte offizielle Java EE 7 tutorial sowie der CDI specification selbst einen Disponenten verwenden, um einen Container-verwalteten Entity Manager zu schließen. Das ist einfach falsch und der Anruf an EntityManager.close() wird eine IllegalStateException egal wohin Sie diesen Anruf, in einer Entsorger-Methode oder woanders werfen. Das Oracle-Beispiel ist der größte Sünder der beiden, indem es die Producer-Klasse als @javax.inject.Singleton deklariert. Wie wir erfahren haben, birgt dieses Risiko die Ursache für den zugrunde liegenden Entity Manager-Verweis auf viele verschiedene Threads.

Es wurde here nachgewiesen, dass durch die unrechtmäßige Verwendung von CDI-Herstellern und -Verwertern 1) der nicht Thread-sichere Entity Manager zu vielen Threads durchgesickert wird und 2) der Disponent keine Wirkung hat; Verlassen des Entity-Managers Was passierte, war die IllegalStateException, die der Container verschluckte, ohne eine Spur davon zu hinterlassen (es wird ein mysteriöser Log-Eintrag gemacht, der besagt, dass ein "Fehler eine Instanz zerstört" hat).

Im Allgemeinen ist die Verwendung von CDI zur Suche nach containerverwalteten Entitätsmanagern keine gute Idee. Die Anwendung ist wahrscheinlich besser dran nur mit @PersistenceContext und sei damit zufrieden. Aber es gibt immer Ausnahmen von der Regel wie in Ihrem Beispiel, und CDI kann auch nützlich sein, um die EntityManagerFactory wegzuzaubern, wenn Sie den Lebenszyklus von anwendungsverwalteten Entitätsmanagern behandeln.

Um ein vollständiges Bild zu erhalten, wie ein Container-verwaltete Einheit Manager zu erhalten und wie CDI verwenden Entity-Manager zum Nachschlagen, könnten Sie this und this lesen mögen.