2013-03-07 12 views
23

Zajmuję się tworzeniem aplikacji webowej, która potrzebuje dostępu do dwóch różnych serwerów baz danych (H2 i Oracle). Kontener to Apache Tomee 1.5.1 i używam stosu Java EE z zapewnionymi bibliotekami (JSF, JPA, CDI, EJB itp.).Dlaczego różne jednostki trwałości z oddzielnymi źródłami danych wysyłają zapytanie do tego samego źródła danych?

Próbuję użyć dwóch menedżerów encji wewnątrz transakcji XA do wyodrębnienia danych z bazy danych Oracle i utrzymania ich w H2 po ich przekształceniu, ALE wszystkie kwerendy są wykonywane względem bazy danych H2 bez względu na zarządcę jednostki I posługiwać się. Jakaś pomoc?

EDYCJA: Odkryłem, że jeśli próbuję uzyskać dostęp do menedżerów podmiotów w odwrotnej kolejności, zachowanie jest takie samo, ale dostęp do Oracle. Np. Zarządcy podmiotu pozostają przy pierwszej dostępnej bazie danych.

EJB gdzie to się dzieje (wywołanie service.getFoo() z 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... 
    } 
} 

Producent zasobu (CDI) dla zarządzających podmiotu (gdzie @ H2Database i @OracleDatabase są qualifiers):

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

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

Moja perystencja.xml wygląda następująco:

<?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> 

I wreszcie, źródła danych wewnątrz tomee.xml (nie ma żadnych innych źródeł danych skonfigurowane wewnątrz tego pliku):

<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

Czy jest to tylko błąd kopiowania i wklejania, czy też błędna nazwa jednostki znajduje się w '@ PercistenceContext'? Czy powinien to być 'FenixRadarPU'? – Magnilex

+0

To jest błąd wklejania kopii. Już to poprawiłem. Dziękuję Ci! –

+2

Dodaj adnotacje '@PersistenceContext (unitName =" ... ")' bezpośrednio do 'EntityManager's w klasie" Service ", aby spróbować ustalić, czy jest to problem CDI, czy problem JPA. –

Odpowiedz

42

Container Managed Persistence kontekstach

Podczas korzystania kontekstów utrwalania kontenerów zarządzane (jak jesteś poprzez adnotacje @PersistenceContext), spec JPA określa, że ​​tylko jeden kontekst wytrwałość może być związana z transakcją JTA.

Kontekst utrwalania jest tworzony przez kontener Java EE. Pomimo pojawienia się kodu (adnotacje @PersistenceContext wydają się sugerować, że komputer jest wstrzykiwany bezpośrednio do zmiennych instancji EntityManager), kontekst utrwalania jest faktycznie przechowywany jako odniesienie W RAMACH TRANSAKCJI JTA. Za każdym razem, gdy pojawia się operacja EntityManager, nie odnosi się ona do własnego wewnętrznego kontekstu utrwalania. Zamiast tego wykonuje operację specjalną, ponieważ jest zarządzana przez kontener - zawsze wyszukuje kontekst trwałości w ramach transakcji JTA i używa go. Nazywa się to propagacją kontekstu utrwalania JTA.

kilka cytatów z spec WZP:

Kiedy używany jest menedżer jednostka pojemnik zarządzane, cykl życia kontekście trwałości zawsze jest zarządzany automatycznie, transparentnie do aplikacji i kontekstu utrwalania jest propagowany za pomocą transakcji JTA .

Pojemnik zarządzane transakcja-scoped Persistence Context

... Nowy kontekst Trwałość zaczyna się, gdy menedżer jednostka pojemnik zarządzane jest wywoływany [76] w zakresie aktywnej transakcji JTA i istnieje brak bieżącego kontekstu utrwalania już powiązanego z transakcją JTA . Kontekst utrwalania jest tworzony, a następnie powiązany z transakcją JTA .

Pojemnik zarządzane szerszym kontekście Trwałość

... Pojemnik zarządzane szerszym kontekście wytrwałość może zostać zainicjowany wyłącznie w zakresie z fasoli sesji stanowej. Istnieje od momentu, w którym stanowy komponent bean sesji , który deklaruje zależność od menedżera encji typu PersistenceContextType.EXTENDED , jest tworzony i jest przypisywany do stanowego komponentu bean sesji. Zależność od rozszerzonego kontekstu utrwalania deklarowana jest za pomocą adnotacji PersistenceContext lub elementu deskryptora wdrażania persist-context-ref. Kontekst utrwalania jest zamykany przez kontener po ukończeniu metody @Remove dla stanowego komponentu bean sesji (lub gdy w innym przypadku stanowa instancja komponentu bean sesji zostanie zniszczona).

Wymagania dotyczące Persistence Context Krzewienia

... Jeśli składnik nazywa i nie ma żadnej transakcji JTA ... kontekst wytrwałość nie jest propagowana. • Wywołanie menedżera encji zdefiniowanego przy użyciu PersistenceContext- Type.TRANSACTION spowoduje użycie nowego kontekstu utrwalania. • Wywołanie menedżera encji zdefiniowanego przy użyciu PersistenceContext- Type.EXTENDED spowoduje użycie istniejącego rozszerzonego kontekstu utrwalania związanego z tym komponentem.

... Jeśli komponent jest wywoływany, a transakcja JTA jest propagowana do tego komponentu: • Jeśli komponent jest stanowym komponentem sesji, do którego został powiązany rozszerzony kontekst utrwalania i istnieje inny kontekst utrwalania związany z transakcja JTA, wyjątek EJBException jest generowany przez kontener. • W przeciwnym razie, jeśli istnieje kontekst utrwalania związany z transakcją JTA, ten kontekst utrwalania jest propagowany i używany.

To jest twój problem. Oczywiste 64 pytanie: DLACZEGO specyfikacja wymaga tego?

Cóż, to dlatego, że jest to celowy kompromis, który przynosi potężną magię EntityManager EJB.

Stosowanie transakcji JTA do propagowania pojedynczego kontekstu utrwalania ma pewne ograniczenia: transakcje nie mogą obejmować wielu kontekstów utrwalania, więc nie mogą przechodzić przez wiele baz danych. Ta cecha ma także olbrzymią zaletę: dowolny podmiotNanager zadeklarowany w komponentach EJB może automatycznie współużytkować ten sam kontekst utrwalania i tym samym może operować na tym samym zbiorze jednostek JPA i uczestniczyć w tej samej transakcji. Możesz mieć łańcuch EJB wywołując inne EJB o dowolnej złożoności i wszystkie zachowują się rozsądnie i konsekwentnie w stosunku do danych jednostki JPA.Nie wymagają one również złożoności stałych odwołań do menedżerów encji/współudziału w wywołaniach metod - EntityManagers mogą być zadeklarowane prywatnie w każdej metodzie. Logika implementacji może być bardzo prosta.

The Answer do Twojego problemu: Zastosowanie Application-Managed Persistence Konteksty (za pośrednictwem aplikacji zarządzane EntityManagers)

Deklarują swoją EntityManager za pośrednictwem jednego z tych podejść:

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

LUB

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

and the same for emfOracle & emOracle.  

Musisz wywołać em.close() po zakończeniu każdego EM - najlepiej przez ostatnią { } lub za pomocą instrukcji try-with-resources Java 7.

EM zarządzane przez aplikacje nadal uczestniczą w (tj. Synchronizują się z) transakcjami JTA. Dowolna liczba EM zarządzanych przez aplikacje może uczestniczyć w pojedynczej transakcji JTA, ale żadna z nich nigdy nie będzie miała kontekstu utrwalania skojarzonego lub propagowanego z żadnym z zarządzanych kontenerami EM.

Jeśli EntityManager tworzony jest poza kontekstem transakcji JTA (zanim rozpoczęto transakcji), a następnie należy go zapytać jawnie przystąpić do transakcji JTA:

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

Albo jeszcze prościej, jeśli EntityManager jest utworzony w kontekście transakcji JTA, wówczas zarządzany przez aplikację EntityManager automatycznie łączy się z implikacją transakcji JTA - nie jest wymagana żadna metoda joinTransaction().

W związku z tym systemy EM zarządzane za pomocą aplikacji mogą zawierać transakcje JTA obejmujące wiele baz danych. Oczywiście, można sprecyzowane uruchomienie lokalnego zasobu JDBC niezależnej transakcji z JTA:

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

// .... 

tx.commit(); 

EDIT: Dodatkowe szczegóły dotyczące zarządzania transakcji z Entity Managers

UWAGA Application-Managed: próbki kodu poniżej są dla wykorzystanie edukacyjne - wypisałem je z mojej głowy, aby pomóc wyjaśnić moje punkty: & nie miałem czasu na kompilację/debugowanie/testowanie.

Domyślnym parametrem @TransactionManagement dla EJB jest TransactionManagement.CONTAINER, a domyślny parametr @TransactionAttribute dla metod EJB to TransactionAttribute.REQUIRED.

Istnieją cztery permutacje zarządzania transakcji:

  • A) z kontenera EJB zarządzanych transakcji JTA

    Jest to korzystne podejście Java EE.
    Adnotacja EJB class @TransactionManagement:
    musi jawnie ustawić TransactionManagement.CONTAINER lub pominąć ją, aby niejawnie używać wartości domyślnej.
    Metoda EJB @ Wartość transakcjiAttribute: musi jawnie ustawić na TransactionAttribute.REQUIRED lub pominąć, aby domniemana wartość była domyślna. (Uwaga: gdybyś miał inny scenariusz biznesowy, mógłbyś użyć TransactionAttribute.MANDATORY lub TransactionAttribute.REQUIRES_NEW, jeśli ich semantyka odpowiadała Twoim potrzebom.)
    Menedżery zarządzające podmiotami zarządzanymi przez aplikacje:
    muszą być tworzone za pomocą Persistence.createEntityManagerFactory ("unitName") i emf.createEntityManager(), jak opisano powyżej.
    Przyłącz się do EntityManagers za pomocą transakcji JTA:
    Utwórz EntityManagers w transakcyjnej metodzie EJB, a oni automatycznie dołączą do transakcji JTA. LUB jeśli EntityManagers są tworzone wcześniej, wywołaj metodę em.joinTransaction() w metodzie EJB transakcji.
    Zadzwoń do EntityManager.close() po zakończeniu korzystania z nich. To wszystko, co potrzebne.

    podstawowe przykłady - wystarczy użyć więcej EntityManagers dla całej transakcji wielokrotnych 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 Bean udało transakcji JTA

    Zastosowanie @ TransactionManagement.BEAN.
    Wprowadź interfejs użytkownika JTA UserTransaction, aby komponent bean mógł bezpośrednio oznaczać transakcje JTA.
    Ręcznie zaznacz/zsynchronizuj transakcję za pomocą metody UserTransaction.begin()/commit()/rollback().
    Upewnij się, że EntityManager dołącza do transakcji JTA - utwórz EM w aktywnym kontekście transakcji JTA LUB wywołaj metodę em.joinTransaction().

    Przykłady:

    @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 z zasobów transakcji lokalnych ręcznie kodowane (fasola udało) (nie JTA)

    Wystarczy użyć interfejsu JPA EntityTransaction dla tx demarkacji (uzyskane za pośrednictwem em.getTransaction()).

    przykład:

    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/dla EJB ręką kodowany (POJO zarządzane) transakcji JTA

    ten zakłada POJO/składnik uruchomiony w jakimś pojemniku, który ma JTA wsparcie.
    Jeśli w kontenerze Java EE można użyć wtrysku zasobów Java EE interfejsu użytkownika JTA JTA.
    (. Można wyraźnie odnośnika uchwyt do interfejsu JTA i do granic na niego, a następnie wywołać em.getTransaction() joinTransaction() - patrz JTA spec.)

    przykład:

    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

To zdecydowanie pomogło. Stworzyłem EMs zgodnie z twoją sugestią i zamknąłem je po wykonaniu zadania, chociaż musiałem dodać adnotację do EJB jako '@TransactionManagement (TransactionManagementType.BEAN)' ponieważ problem "pojedynczej transakcji JTA" wciąż miał miejsce z '@TransactionManagement (TransactionManagementType.CONTAINER) '. Ponadto otrzymuję teraz wyjątek 'org.apache.openjpa.persistence.TransactionRequiredException: może wykonywać operacje tylko w czasie, gdy transakcja jest aktywna. Wygląda na to, że muszę ręcznie utworzyć/zatwierdzić/wycofać/zamknąć transakcję. Czy to jest poprawne? –

+0

Cieszę się, że pomogło. JEŚLI używasz @TransactionManagement (TransactionManagementType.BEAN, musisz zrobić własną demarkację/synchronizację transakcji JTA: –

+0

... w rzeczywistości - zapomnij o tym komentarzu: dodałem do końca postu powyżej zamiast tego B ^) –

-2

spróbować stworzyć najpierw zapytanie nie natywną zapytanie, zwracając lista barów. Spróbuj również skomentować wtrysk H2 w EJB. Jeśli to działa, to wiesz, że jest to problem z konfliktem CDI.

+0

Skomentowałem 'emH2' i jego adnotacje i zrobiłem tylko kwerendę JPQL na' emOracle'. Udało się, ale to nie jest rozwiązanie. Nie rozumiem, w jaki sposób obie jednostki trwałości/źródła danych/kwalifikatory są w konflikcie, gdy wyraźnie odnoszą się do różnych rzeczy. –

+0

Upewnij się, że klasa Bar znajduje się w liście, którą wymieniłeś. Spróbuj z klasami true dla obu PU. – javadev

+0

Pakiety są w porządku. Klasy ' true' są zawarte w _persistence.xml_, jak widać. –

Powiązane problemy