2013-03-07 12 views
23

Sto sviluppando una webapp che richiede l'accesso a due diversi server di database (H2 e Oracle). Il contenitore è un Apache Tomee 1.5.1 e sto usando lo stack Java EE con le librerie fornite in esso (JSF, JPA, CDI, EJB, ecc.).Perché le diverse unità di persistenza con origini dati separate interrogano la stessa origine dati?

Sto cercando di utilizzare due gestori di entità all'interno di una transazione XA per estrarre i dati dal database Oracle e mantenerli in H2 dopo averlo trasformato, MA tutte le query vengono eseguite sul database H2 indipendentemente dal gestore entità I uso. Qualsiasi aiuto?

EDIT: Ho scoperto che se provo ad accedere ai gestori di entità in ordine inverso, il loro comportamento è lo stesso, ma l'accesso a Oracle. I.e: i gestori delle entità rimangono con il primo database a cui si accede.

L'EJB dove questo accade (chiamando service.getFoo() da JSF):

@Named 
@Stateless 
public class Service { 
    @Inject 
    @OracleDatabase 
    private EntityManager emOracle; 

    @Inject 
    @H2Database 
    private EntityManager emH2; 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public List<Foo> getFoo() { 
     TypedQuery<Foo> q = emH2.createQuery(
       "SELECT x FROM Foo f", Foo.class); 
     List<Foo> l = q.getResultList(); 
     if (l == null || l.isEmpty()) { 
      update(); 
     } 

     return q.getResultList(); 
    } 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public void update() { 
     // FAIL: This query executes against H2 with Oracle entity manager! 
     List<Object[]> l = emOracle.createNativeQuery("SELECT * FROM bar ").getResultList(); 

     //more stuff... 
    } 
} 

Il produttore delle risorse (CDI) per i gestori di entità (dove @ H2Database e @OracleDatabase sono qualifiers):

public class Resources { 
    @Produces 
    @PersistenceContext(unitName = "OraclePU") 
    @OracleDatabase 
    private EntityManager emOracle; 

    @Produces 
    @PersistenceContext(unitName = "H2PU") 
    @H2Database 
    private EntityManager emH2; 
} 

mio peristence.xml assomiglia a questo:

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.0" 
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 
    <persistence-unit name="H2PU" 
     transaction-type="JTA"> 
     <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> 
     <jta-data-source>H2DS</jta-data-source> 
     <class>my.app.h2.Foo</class> 
     <exclude-unlisted-classes>true</exclude-unlisted-classes> 
    </persistence-unit> 

    <persistence-unit name="OraclePU" transaction-type="JTA"> 
     <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> 
     <jta-data-source>OracleDS</jta-data-source> 
     <class>my.app.oracle.Bar</class> 
     <exclude-unlisted-classes>true</exclude-unlisted-classes> 
    </persistence-unit> 
</persistence> 

E, infine, le fonti di dati all'interno tomee.xml (ma non ci sono altre fonti di dati configurate all'interno di questo file):

<Resource id="OracleDS" type="javax.sql.DataSource"> 
    jdbcDriver = oracle.jdbc.xa.client.OracleXADataSource 
    jdbcUrl = jdbc:oracle:thin:@server:port:instance 
    jtaManaged = true 
    password = abcde 
    userName = user 
</Resource> 

<Resource id="H2DS" type="javax.sql.DataSource"> 
    jdbcDriver=org.h2.jdbcx.JdbcDataSource 
    jdbcUrl=jdbc:h2:h2/db;AUTO_SERVER=TRUE 
    jtaManaged = true 
    password = edcba 
    userName = user 
</Resource> 
+0

è solo un errore di copia-incolla, o avete il nome dell'unità di sbagliato nel vostro '@ PercistenceContext'? Dovrebbe essere 'FenixRadarPU'? – Magnilex

+0

E 'un errore di copia incolla. L'ho già corretto Grazie! –

+2

Aggiungere il '@PersistenceContext (unitName = "...")' annotazioni direttamente ai 'EntityManager's nella classe' Service' per cercare di determinare se questo è un problema CDI, o un problema di JPA. –

risposta

42

Container Managed Persistence Contesti

Quando si utilizza contesti di persistenza gestiti dal contenitore (come siete tramite annotazioni @PersistenceContext), la specifica JPA specifica che solo un contesto di persistenza può essere associato ad una transazione JTA.

Il contesto di persistenza viene creato dal contenitore Java EE. Nonostante le apparenze del codice (le annotazioni @PersistenceContext sembrano suggerire che il PC sia iniettato direttamente nelle variabili dell'istanza di EntityManager), il contesto di persistenza viene effettivamente memorizzato come riferimento ENTRO LA TRANSAZIONE JTA. Ogni volta che un'operazione di EntityManager si verifica, non fa riferimento al proprio contesto di persistenza interno. Invece esegue un'operazione speciale perché è gestita dal contenitore: cerca sempre il contesto di persistenza all'interno della transazione JTA e la usa. Questo è chiamato propagazione del contesto di persistenza JTA.

alcune citazioni dalla specifica JPA:

Quando si utilizza un gestore di entità gestita dal contenitore, il ciclo di vita del contesto persistenza è sempre gestito automaticamente, in modo trasparente per l'applicazione, e il contesto di persistenza viene propagato con la transazione JTA .

Container gestiti Transaction-ambito contesto di persistenza

... Un nuovo contesto di persistenza inizia quando il contenitore gestiti entità responsabile viene richiamato [76] nell'ambito di una transazione JTA attiva, e c'è nessun contesto di persistenza corrente già associato alla transazione JTA . Il contesto di persistenza viene creato e quindi associato a con la transazione JTA.

Container gestiti esteso contesto di persistenza

... un contesto di persistenza esteso gestita dal contenitore può essere avviata solo nell'ambito di un session bean stateful. Esiste dal punto in cui il bean di sessione stateful che dichiara una dipendenza da un gestore di entità di tipo PersistenceContextType.EXTENDED viene creato e si dice che sia associato al bean di sessione stateful. La dipendenza dal contesto di persistenza esteso viene dichiarata mediante l'annotazione PersistenceContext o l'elemento descrittore di deployment ref contest di persistenza. Il contesto di persistenza viene chiuso dal contenitore quando il metodo @Remove del bean di sessione stateful viene completato (oppure l'istanza di bean di sessione stateful viene altrimenti distrutta).

Requisiti per Persistence Context Propagazione

... Se un componente si chiama e non v'è nessuna transazione JTA ..., il contesto di persistenza non è propagato. • Il richiamo di un gestore di entità definito con PersistenceContext- Type.TRANSACTION comporterà l'uso di un nuovo contesto di persistenza. • Il richiamo di un gestore di entità definito con PersistenceContext- Type.EXTENDED comporterà l'utilizzo del contesto di persistenza esteso esistente associato a quel componente.

... Se un componente viene chiamato e la transazione JTA viene propagata in quel componente: • Se il componente è un bean di sessione stateful a cui è stato associato un contesto di persistenza esteso e vi è un diverso contesto di persistenza associato a la transazione JTA, un EJBException viene lanciato dal contenitore. • Altrimenti, se esiste un contesto di persistenza associato alla transazione JTA, tale contesto di persistenza viene propagato e utilizzato.

Quindi questo è il tuo problema. L'ovvia domanda da $ 64: perché la specifica lo richiede ???

Bene, è perché è un compromesso intenzionale che porta la potente magia di EntityManager agli EJB.

Utilizzando le transazioni JTA per propagare un unico contesto di persistenza ha una limitazione: le transazioni non possono cavalcare molteplici contesti di persistenza, in modo da non possono cavalcare più database.

Tuttavia, ha anche un enorme vantaggio: qualsiasi entityManager dichiarato negli EJB può condividere automaticamente lo stesso contesto di persistenza e quindi può operare sullo stesso set di entità JPA e partecipare alla stessa transazione. È possibile avere una catena di EJB che richiama altri bean di qualsiasi complessità e si comportano tutti in modo ragionevole e coerente rispetto ai dati dell'entità JPA.Inoltre, non richiedono la complessità di intensionare/condividere in modo coerente i riferimenti del gestore di entità attraverso invocazioni di metodi: gli EntityManager possono essere dichiarati privatamente in ciascun metodo. La logica di implementazione può essere molto semplice.

La risposta al vostro problema: utilizzare Application-Managed Persistence contesti (via EntityManagers applicativi gestiti)

dichiarare il vostro entityManager tramite uno di questi approcci:

// "Java EE style" declaration of EM 
@PersistenceUnit(unitName="H2PU") 
EntityManagerFactory emfH2; 
EntityManager emH2 = emfH2.createEntityManager(); 

O

// "JSE style" declaration of EM 
EntityManagerFactory emfH2 = javax.persistence.Persistence.createEntityManagerFactory("H2PU"); 
EntityManager emH2 = emfH2.createEntityManager(); 

and the same for emfOracle & emOracle.  

È necessario chiamare em.close() al termine di ogni EM, preferibilmente tramite un finale { } clausola o tramite un'istruzione try-with-resources di Java 7.

Gli EM gestiti dall'applicazione continuano a partecipare (cioè a sincronizzare con) le transazioni JTA. Qualsiasi numero di EM gestiti da applicazioni può partecipare a una singola transazione JTA, ma nessuno di questi avrà mai il proprio contesto di persistenza associato o propagato a qualsiasi contenitore gestito EM.

Se l'EntityManager viene creata al di fuori del contesto di una transazione JTA (prima che la transazione è iniziata), allora si deve chiedere che esplicitamente di aderire alla transazione JTA:

// must be run from within Java EE code scope that already has a JTA 
// transaction active: 
em.joinTransaction(); 

O ancora più semplice, se l'EntityManager è creato all'interno del contesto di una transazione JTA, quindi EntityManager gestito dall'applicazione si unisce automaticamente all'implicazione della transazione JTA - non è necessario joinTransaction().

Quindi gli EM gestiti dall'applicazione possono disporre di una transazione JTA che si trova a cavallo di più database. Naturalmente, si può sempre puntuale esegue un locale risorsa JDBC transazione indipendente JTA:

EntityTransaction tx = em.getTransaction(); 
tx.begin(); 

// .... 

tx.commit(); 

EDIT: dettagli in più per Transaction Management con Entity Manager

ATTENZIONE applicativi gestiti: i campioni il codice qui sotto sono per uso educativo - Li ho digitati in cima alla mia testa per aiutare a spiegare i miei punti & non hanno avuto il tempo di compilare/debug/test.

Il parametro predefinito @TransactionManagement per EJB è TransactionManagement.CONTAINER e il parametro predefinito @TransactionAttribute per i metodi EJB è TransactionAttribute.REQUIRED.

ci sono quattro permutazioni per la gestione delle transazioni:

  • A) EJB con Container Managed transazioni JTA

    Questo è l'approccio preferito Java EE.
    Classe EJB @TransactionGestione gestione:
    deve essere impostato su TransactionManagement.CONTAINER in modo esplicito o omesso per utilizzare implicitamente il valore predefinito.
    Metodo EJB @TransactionAttribute annotazione: deve essere impostato su TransactionAttribute.REQUIRED in modo esplicito oppure ometterlo a implicito utilizzare il valore predefinito. (Nota: se avessi uno scenario di business diverso, potresti utilizzare TransactionAttribute.MANDATORY o TransactionAttribute.REQUIRES_NEW se la loro semantica corrisponde alle tue esigenze.)
    Gestori entità gestite dall'applicazione:
    devono essere creati tramite Persistence.createEntityManagerFactory ("unitName") ed emf.createEntityManager(), come descritto sopra.
    Unire gli EntityManager con la transazione JTA:
    Creare gli EntityManagers ENTRO un metodo EJB transazionale e si uniranno automaticamente alla transazione JTA. OPPURE se EntityManagers viene creato in precedenza, chiamare em.joinTransaction() all'interno di un metodo EJB della transazione.
    EntityManager.close call() quando sono finiti li utilizzano. Questo dovrebbe essere tutto ciò che è richiesto.

    esempi di base - basta usare più EntityManagers per la transazione su più DB:

    @Stateless 
    public class EmployeeServiceBean implements EmployeeService { 
    
        // Transactional method 
        public void createEmployee() { 
         EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService"); 
         EntityManager em = emf.createEntityManager(); 
         Employee emp = ...; // set some data 
         // No need for manual join - em created in active tx context, automatic join: 
         // em.joinTransaction();   
         em.persist(emp); 
         // other data & em operations ... 
         // call other EJBs to partake in same transaction ... 
         em.close(); // Note: em can be closed before JTA tx committed. 
            // Persistence Context will still exist & be propagated 
            // within JTA tx. Another EM instance could be declared and it 
            // would propagate & associate the persistence context to it. 
            // Some time later when tx is committed [at end of this 
            // method], Data will still be flushed and committed and 
            // Persistence Context removed . 
        emf.close(); 
        } 
    
    } 
    
    
    
    @Stateful 
    public class EmployeeServiceBean implements EmployeeService { 
    
        // Because bean is stateful, can store as instance vars and use in multiple methods 
        private EntityManagerFactory emf; 
        private EntityManager em; 
    
        @PostConstruct  // automatically called when EJB constructed and session starts 
        public void init() { 
         emf = Persistence.createEntityManagerFactory("EmployeeService"); 
         em = emf.createEntityManager(); 
        } 
    
        // Transactional method 
        public void createEmployee() { 
         Employee emp = ...; // set some data 
         em.joinTransaction();   // em created before JTA tx - manual join 
         em.persist(emp); 
        } 
    
        // Transactional method 
        public void updateEmployee() { 
         Employee emp = em.find(...); // load the employee 
         // don't do join if both methods called in same session - can only call once: 
         // em.joinTransaction();   // em created before JTA tx - manual join 
         emp.set(...);     // change some data 
              // no persist call - automatically flushed with commit 
        } 
    
        @Remove       // automatically called when EJB session ends 
        public void cleanup() { 
         em.close(); 
         emf.close(); 
        } 
    // ... 
    } 
    
  • B) EJB con bean gestito transazioni JTA

    Usa @ TransactionManagement.BEAN.
    Iniettare l'interfaccia UserTransaction JTA, quindi il fagiolo può contrassegnare direttamente le transazioni JTA.
    marchio manualmente/sincronizzare la transazione tramite UserTransaction.begin()/commit()/rollback().
    Verificare l'EntityManager unisce la transazione JTA - sia creare la EM in un contesto di transazione JTA attiva o chiamare il numero em.joinTransaction().

    Esempi:

    @TransactionManagement(TransactionManagement.BEAN) 
    @Stateless 
    public class EmployeeServiceBean implements EmployeeService { 
    
        // inject the JTA transaction interface 
        @Resource UserTransaction jtaTx; 
    
        public void createEmployee() { 
         EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService"); 
         EntityManager em = emf.createEntityManager(); 
         try { 
          jtaTx.begin(); 
          try { 
           em.joinTransaction();   
           Employee emp = ...; // set some data 
           em.persist(emp); 
           // other data & em operations ... 
           // call other EJBs to partake in same transaction ... 
          } finally { 
           jtaTx.commit(); 
          } 
         } catch (Exception e) { 
          // handle exceptions from UserTransaction methods 
          // ... 
         } 
    
         Employee emp = ...; // set some data 
         // No need for manual join - em created in active tx context, automatic join: 
         // em.joinTransaction();   
         em.persist(emp); 
         em.close(); // Note: em can be closed before JTA tx committed. 
            // Persistence Context will still exist inside JTA tx. 
            // Data will still be flushed and committed and Persistence 
            // Context removed some time later when tx is committed. 
         emf.close(); 
        } 
    
    } 
    
  • C) POJO/non-EJB con le transazioni (bean gestito) codificati a mano delle risorse locali (non JTA)

    È sufficiente utilizzare l'interfaccia JPA EntityTransaction per TX demarcazione (ottenuto tramite em.getTransaction()).

    Esempio:

    public class ProjectServlet extends HttpServlet { 
    
        @EJB ProjectService bean; 
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException { 
         // ... 
         try { 
          EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU"); 
          EntityManager em = emf.createEntityManager(); 
          EntityTransaction tx = em.getTransaction(); 
          tx.begin(); 
          try { 
           bean.assignEmployeeToProject(projectId, empId); 
           bean.updateProjectStatistics(); 
          } finally { 
           tx.commit(); 
          } 
         } catch (Exception e) { 
          // handle exceptions from EntityTransaction methods 
          // ... 
         } 
        // ... 
        } 
    } 
    
  • D) POJO/non-bean con() transazioni JTA POJO gestiti codificati a mano

    Questo presuppone che il POJO/componente è in esecuzione in qualche contenitore che ha JTA supporto.
    Se in un container Java EE, è possibile utilizzare Java EE risorsa iniezione di interfaccia UserTransaction JTA.
    (. In alternativa, può cercare in modo esplicito una maniglia per l'interfaccia JTA e fare la demarcazione su di esso, quindi chiamare em.getTransaction() JoinTransaction() - vedi JTA spec.)

    Esempio:

    public class ProjectServlet extends HttpServlet { 
    
        @Resource UserTransaction tx; 
        @EJB ProjectService bean; 
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException { 
         // ... 
         try { 
          tx.begin(); 
          try { 
           bean.assignEmployeeToProject(projectId, empId); 
           bean.updateProjectStatistics(); 
           EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU"); 
           EntityManager em = emf.createEntityManager(); 
           // Should be able to avoid explicit call to join transaction. 
           // Should automatically join because EM created in active tx context. 
           // em.joinTransaction(); 
           // em operations on data here 
           em.close(); 
           emf.close(); 
          } finally { 
           tx.commit(); 
          } 
         } catch (Exception e) { 
          // handle exceptions from UserTransaction methods 
          // ... 
         } 
        // ... 
        } 
    } 
    
+0

Questo sicuramente ha aiutato. Ho creato gli EM come suggerito e li ho chiusi dopo che il lavoro è terminato, anche se ho dovuto annotare l'EJB come '@TransactionManagement (TransactionManagementType.BEAN)' poiché il problema della "singola transazione JTA" stava ancora accadendo con '@TransactionManagement (TransactionManagementType.CONTAINER) '. Inoltre, sto ricevendo questa eccezione 'org.apache.openjpa.persistence.TransactionRequiredException: è possibile eseguire l'operazione solo mentre una transazione è attiva'. Sembra che ho bisogno di creare/commit/rollback/chiudere la transazione manualmente. È corretto? –

+0

Felice di averlo aiutato. SE si utilizza @TransactionManagement (TransactionManagementType.BEAN, allora è necessario eseguire la propria demarcazione/sincronizzazione della transazione JTA: –

+0

... in realtà - dimenticare quel commento: ho aggiunto alla fine del post sopra invece B ^) –

-2

tenta di creare una query non una query nativa prima di ritorno, una elenco di barre. Prova anche a commentare l'iniezione di H2 nel tuo EJB. Se funziona, allora sai che è un problema di conflitto CDI.

+0

Ho commentato 'emH2' e le sue annotazioni e ho fatto solo una query JPQL su' emOracle'. Ha funzionato, ma non è una soluzione. Non capisco come entrambe le unità di persistenza/origini dati/qualificatori siano in conflitto quando si riferiscono chiaramente a cose diverse. –

+0

Assicurati che la classe Bar sia nel pacchetto che hai elencato. Prova con true per entrambe le PU. – javadev

+0

I pacchetti vanno bene. Il ' true' è incluso nel _persistence.xml_ come puoi vedere. –

Problemi correlati