Dlaczego różne jednostki trwałości z oddzielonymi źródłami danych pytają o to samo źródło danych?

Rozwijam webapp, który wymaga dostępu do dwóch różnych serwerów baz danych (H2 i Oracle). Kontener jest Apache Tomee 1.5.1 i używam stosu Java EE z dołączonymi w nim bibliotekami (JSF, JPA, CDI, EJB, itp.).

Próbuję użyć dwóch menedżerów encji wewnątrz transakcji XA, aby wyodrębnić dane z bazy danych Oracle i utrzymać je w H2 po przekształceniu, ale wszystkie zapytania są wykonywane przeciwko bazie danych H2 bez względu na menedżera encji, którego używam. Jakaś pomoc?

EDIT : odkryłem, że jeśli spróbuję uzyskać dostęp do menedżerów encji w odwrotnej kolejności, ich zachowanie jest takie samo, ale dostęp do Oracle. Czyli: menedżerowie podmiotu pozostają z pierwszą dostępną bazą 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 zasobów (CDI) dla menedżerów jednostek (gdzie @H2Database i @OracleDatabase są kwalifikatorami ):

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

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

Moja perystencja.XML wygląda jak to:

<?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 skonfigurowanych w tym 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>
Author: beto, 2013-03-07

2 answers

Container Managed Persistence Contexts

Podczas korzystania z kontekstów trwałości zarządzanych kontenerem (tak jak w przypadku adnotacji @PersistenceContext), Specyfikacja JPA określa, że tylko jeden kontekst trwałości może być powiązany z transakcją JTA.

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

Niektóre cytaty ze specyfikacji JPA:

gdy kontener-zarządzany entity manager jest używany, cykl życia kontekst persistence jest zawsze zarządzany automatycznie, przejrzyście do aplikacji, a kontekst trwałości propagowany jest z Transakcja JTA.

Container-managed Transaction-scoped Persistence Context

... nowy kontekst trwałości rozpoczyna się, gdy menedżer encji zarządzanych kontenerem jest wywoływana [76] w ramach aktywnej transakcji JTA, a jest brak aktualnego kontekstu trwałości już związane z JTA transakcja. Kontekst trwałości jest tworzony, a następnie powiązany z transakcją JTA.

Container-managed Extended Persistence Context

... Kontekst rozszerzonej trwałości zarządzanej kontenerem może być inicjowany tylko w zakresie z sesji statutowej. Istnieje od momentu, w którym stan sesji deklaruje zależność od menedżera jednostek typu PersistenceContextType.EXTENDED na stworzony i mówi się, że jest związany z sesją stanową. Zależność od Rozszerzony kontekst trwałości deklarowany jest za pomocą adnotacji PersistenceContext lub elementu deskryptora wdrożenia persistence-context-ref. Kontekst persistence jest zamykany przez kontener, gdy zakończy się metoda @ Remove stateful session bean (lub instancja stateful session bean jest w inny sposób zniszczona).

Wymagania dotyczące propagacji kontekstu trwałości

... Jeśli komponent jest wywołany i nie ma transakcji JTA ..., kontekst trwałości nie jest propagowany. * Wywołanie menedżera encji zdefiniowanego za pomocą PersistenceContext- Typ.Transakcja spowoduje użycie nowego kontekstu trwałości. * Wywołanie menedżera encji zdefiniowanego za pomocą PersistenceContext- Typ.EXTENDED spowoduje wykorzystanie istniejącego kontekstu Extended persistence związany z tym komponentem.

... Jeśli wywołany jest komponent, a transakcja JTA jest propagowane do tego komponentu: * Jeśli komponent jest stateful session bean, do którego został powiązany Rozszerzony kontekst trwałości i istnieje inny kontekst trwałości związany z transakcją JTA, kontener wyrzuca EJBException. * W przeciwnym razie, jeśli do transakcji JTA związany jest kontekst trwałości, kontekst ten jest propagowany i używany.

Więc to twój problem. Oczywiste pytanie: dlaczego spec prosi o to???

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

Używanie transakcji JTA do propagowania pojedynczego kontekstu trwałości ma ograniczenie: transakcje nie mogą obejmować wielu kontekstów trwałości, więc nie mogą obejmować wielu baz danych.

Ma jednak również ogromną zaletę: każdy entityManager zadeklarowany w EJBs może automatycznie współdzielić ten sam kontekst trwałości i dlatego może działać na ten sam zestaw podmiotów JPA i udział w tej samej transakcji. Możesz mieć łańcuch EJB wywołujący Inne EJB o dowolnej złożoności i wszystkie one zachowują się rozsądnie i konsekwentnie wobec danych jednostki JPA. Nie potrzebują również złożoności konsekwentnego inicjalizowania / udostępniania odwołań menedżera jednostek w różnych wywołaniach metod - Entitymanager mogą być zadeklarowane prywatnie w każdej metodzie. Logika implementacji może być bardzo prosta.

Odpowiedź Na Twój Problem: Użyj Konteksty trwałości zarządzanej przez aplikację (poprzez zarządzane przez aplikację Entitymanagery)

Zadeklaruj swój entityManager za pomocą jednego z następujących sposobów:

// "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 do nich zadzwonić.close() po zakończeniu pracy z każdym EM-najlepiej poprzez ostateczną klauzulę { } lub poprzez instrukcję Java 7 try-with-resources.

Zarządzany przez aplikację EMs nadal uczestniczy w (tj. synchronizuje się z) transakcjami JTA. Dowolna liczba zarządzanych aplikacji EMs może uczestniczyć w jednym Transakcja JTA - ale żaden z nich nigdy nie będzie miał kontekstu trwałości związanego z żadnym kontenerem zarządzanym EM .

Jeśli EntityManager jest tworzony poza kontekstem transakcji JTA (przed rozpoczęciem transakcji), to musisz poprosić go o dołączenie do transakcji JTA:

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

Lub jeszcze prościej, jeśli EntityManager jest tworzony w kontekście transakcji JTA, wtedy zarządzany przez aplikację EntityManager jest automatycznie dołącza do implicity transakcji JTA - nie jest wymagana funkcja joinTransaction ().

Więc zarządzany przez aplikację EMs może mieć transakcję JTA, która łączy wiele baz danych. Oczywiście, można zawsze uruchomić lokalną transakcję JDBC zasobów niezależnych od JTA:

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

// ....

tx.commit();

EDIT: dodatkowe informacje dotyczące zarządzania transakcjami z menedżerami zarządzanymi przez aplikacje

Uwaga: poniższe przykłady kodu są do użytku edukacyjnego - wpisałem je z czubka głowy, aby pomóc wyjaśnić moje punkty i nie miałem czasu na kompilację/debugowanie / testowanie.

Domyślnym parametrem @ TransactionManagement dla EJBs jest TransactionManagement.Kontener i domyślny parametr @ TransactionAttribute dla metod EJB to TransactionAttribute.Wymagane.

Istnieją cztery permutacje do zarządzania transakcjami:

  • A) EJB z transakcjami JTA zarządzanymi przez kontenery

    Jest to preferowane podejście Java EE.
    EJB class @TransactionManagement adnotacja:
    należy ustawić na zarządzanie transakcją.Kontener jawnie lub pomija go, aby domyślnie użyć wartości domyślnej.
    Metoda EJB @ TransactionAttribute adnotacja: musi być ustawione na TransactionAttribute.Wymagane jawnie lub pomiń go do implicity użyj wartości domyślnej. (Uwaga: Jeśli masz inny scenariusz biznesowy, możesz użyć TransactionAttribute.Obowiązkowe lub transakcyjne.REQUIRES_NEW jeśli ich semantyka pasowała do Twoich potrzeb.)
    Menedżerowie podmiotów zarządzanych aplikacjami:
    oni musi być stworzony przez wytrwałość.createEntityManagerFactory ("unitName") i emf.createEntityManager (), jak opisano powyżej.
    Dołącz do EntityManagers z transakcją JTA:
    Utwórz Entitymanagery w metodzie transakcyjnej EJB, a automatycznie dołączą do transakcji JTA. Lub jeśli Entitymanagery zostały utworzone wcześniej, zadzwoń do nich.joinTransaction() w metodzie EJB transakcji.
    Zadzwoń Do EntityManager.close () po zakończeniu ich używania. To powinno być wszystko, co jest wymagane.

    Podstawowe przykłady - wystarczy użyć więcej Entitymanagerów do transakcji w wielu DBs:

    @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 with bean managed JTA transactions

    Użyj @ TransactionManagement.BEAN.
    Inject the JTA usertransaction interface, so the bean can directly marked JTA transactions.
    Ręcznie zaznacz/zsynchronizuj transakcję za pomocą UserTransaction.begin ()/commit () / rollback ().
    Upewnij się, że EntityManager dołączy do transakcji JTA-albo utwórz EM w kontekście aktywnej transakcji JTA lub zadzwoń do niego.jointtransaction ().

    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();
        }
    
    }
    
  • [8]} C) POJO / Non-EJB z ręcznie kodowanymi (bean managed) resource local transactions (nie JTA)

    Wystarczy użyć interfejsu JPA EntityTransaction do rozgraniczenia tx (uzyskanego przez 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 / Non-EJB z ręcznie kodowanymi (zarządzanymi) transakcjami JTA

    Zakłada to, że POJO/komponent działa w jakiś kontener z obsługą JTA.
    Jeśli w kontenerze Java EE, można użyć Java EE resource injection interfejsu JTA UserTransaction.
    (Alternatywnie, może jawnie wyszukać uchwyt do interfejsu JTA i zrobić demarkację na nim, a następnie wywołać je.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
               // ...
            }
        // ...
        }
    }
    
 41
Author: Glen Best,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-17 11:09:09

Spróbuj najpierw utworzyć zapytanie, a nie natywne, zwracając listę pasków. Spróbuj również skomentować wstrzyknięcie H2 w EJB. Jeśli to zadziała, to wiesz, że jest to problem z konfliktem CDI.

 -2
Author: javadev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-11 14:25:22