2013-03-07 2 views
23

Я разрабатываю webapp, который нуждается в доступе к двум различным серверам баз данных (H2 и Oracle). Контейнер - это Apache Tomee 1.5.1, и я использую стек Java EE с предоставленными в нем библиотеками (JSF, JPA, CDI, EJB и т. Д.).Почему разные единицы сохранения с отдельными источниками данных запрашивают один и тот же источник данных?

Я пытаюсь использовать два менеджера сущностей внутри транзакции XA для извлечения данных из базы данных Oracle и сохранения ее в H2 после ее преобразования, НО все запросы выполняются в базе данных H2 независимо от того, использовать. Любая помощь?

EDIT: Я обнаружил, что если я попытаюсь получить доступ к менеджерам сущностей в обратном порядке, они будут одинаковыми, но будут обращаться к Oracle. I.e .: Менеджеры сущностей остаются с первой доступной к базе данных.

EJB-где это происходит (вызов service.getFoo() из 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... 
    } 
} 

Производитель ресурсов (CDI) для менеджеров сущностей (где @ H2Database и @OracleDatabase являются qualifiers):

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

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

My peristence.xml выглядит следующим образом:

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

И, наконец, источники данных внутри tomee.xml (нет никаких других источников данных, настроенные в этом файл):

<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

Это просто ошибка копирования или вхождение в имя '@ PercencyContext'? Должно ли это быть «FenixRadarPU»? – Magnilex

+0

Это ошибка копирования. Я уже исправил это. Спасибо! –

+2

Добавьте '@PersistenceContext (unitName =" ... ")' аннотации непосредственно в 'EntityManager' в классе 'Service', чтобы попытаться определить, является ли это проблемой CDI или проблемой JPA. –

ответ

42

Container Managed Persistence контексты

При использовании управляемого контейнера сохраняемости контекстов (как вы через @PersistenceContext аннотаций), спецификация JPA определяет, что только один контекст сохранения может быть связан с транзакцией JTA.

Контекст сохранения создается контейнером Java EE. Несмотря на появление кода (аннотации @PersistenceContext, похоже, предполагают, что ПК вводится непосредственно в переменные экземпляра EntityManager), контекст персистентности фактически хранится в качестве ссылки WITHIN JTA TRANSACTION. Каждый раз, когда происходит операция EntityManager, она не ссылается на собственный внутренний контекст сохранения. Вместо этого он выполняет специальную операцию, поскольку управляется контейнером - он всегда ищет контекст персистентности в транзакции JTA и использует это. Это называется распространением контекста сохранения JTA.

Некоторые Цитаты из JPA спецификации:

Когда контейнер управляемого менеджер объекта используется, жизненный цикл контекста настойчивости всегда управляются автоматически, прозрачно для приложения, и контекста постоянства распространяется с транзакцией JTA .

Контейнер управляемых транзакции в области видимости Стойкость Контекст

... нового контекст сохранения начинается, когда контейнер управляемого диспетчер объектов вызываются [76] в рамках активной транзакции JTA, и существует контекст текущей стойкости, уже связанный с транзакцией JTA . Контекст сохранения создается, а затем связан с транзакцией JTA.

Контейнер управляемого Extended персистенции Context

... Контейнер управляемого расширенный контекст сохранения может быть инициирован только в рамках из сессионного компонента. Он существует с того момента, когда бит состояния с состоянием , который объявляет зависимость от диспетчера сущности типа PersistenceContextType.EXTENDED , создается и, как говорят, привязан к сеансовому компоненту с состоянием. Зависимость от расширенного контекста постоянных объявлений объявляется с помощью дескриптора дескриптора PersistenceContext или persistence-context-ref. Контекст сохранения закрывается контейнером при завершении метода @Remove сеансового компонента stateful (или экземпляр компонента состояния stateful в противном случае уничтожается).

Требования к Persistence Контекст распространения

... Если компонент называется и нет никакой транзакции JTA ..., контекст живучесть не распространяется. • Вызов менеджера объектов, определенного с помощью параметра PersistenceContext- . Type.TRANSACTION приведет к использованию нового контекста персистентности. • Вызов менеджера объектов, определенного в PersistenceContext- . Тип.EXTENDED приведет к использованию существующего расширенного контекста постоянства , связанного с этим компонентом.

... Если компонент вызывается и транзакция JTA распространяется на этот компонент: • Если компонент представляет собой сеансовый компонент с состоянием, к которому был привязан расширенный контекст персистентности, и существует другой контекст постоянства, связанный с транзакция JTA, исключение EJBException выбрано контейнером. • В противном случае, если существует контекст постоянства, связанный с транзакцией JTA, этот контекст персистентности распространяется и используется.

Это ваша проблема. Очевидный вопрос в $ 64: ПОЧЕМУ spec спрашивает об этом ???

Ну, это потому, что это преднамеренный компромисс, который приносит мощные магии EntityManager для EJB.

Использование транзакций JTA для распространения одного контекста постоянства имеет ограничение: транзакции не могут охватывать несколько контекстов постоянства, поэтому не могут охватывать несколько баз данных.

Однако это также имеет огромное преимущество: любой entityManager, объявленный в EJB, может автоматически обмениваться одним и тем же контекстом персистентности и, следовательно, может работать с одним и тем же набором объектов JPA и участвовать в одной транзакции. У вас может быть цепочка EJB, вызывающая другие EJB любой сложности, и все они ведут себя разумно и последовательно против данных сущности JPA.И им также не нужна сложность последовательной инициализации/обмена ссылками диспетчера объектов между вызовами метода - EntityManager может быть объявлен конфиденциально в каждом методе. Логика реализации может быть очень простой.

Ответ на ваш вопрос: Использование Применение управляемого Постоянство контексты (с помощью приложения управляемого EntityManagers)

Объявить свой EntityManager с помощью одного из этих подходов:

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

ИЛИ

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

and the same for emfOracle & emOracle.  

Вы должны называть em.close(), когда вы закончите с каждым EM - предпочтительнее через окончательный { } или с помощью инструкции Java 7 try-with-resources.

Управляемые приложением ЭМ все еще участвуют (например, синхронизируются с) транзакциями JTA. Любое количество управляемых приложениями EM может участвовать в одной транзакции JTA, но ни один из них никогда не будет иметь свой контекст персистентности, связанный или распространяемый с любым управляемым контейнером EM.

Если EntityManager создается вне контекста транзакции JTA (до сделки началась), то вы должны спросить его в явной форме присоединиться к транзакции JTA:

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

Или еще проще, если EntityManager является созданный в контексте транзакции JTA, тогда EntityManager, управляемый приложениями, автоматически присоединяется к импликации транзакций JTA - нет необходимости в joinTransaction().

Таким образом, управляемые EM могут иметь транзакцию JTA, которая охватывает несколько баз данных. Конечно, вы можете всегда запустить локальный ресурс JDBC транзакций независимо от JTA:

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

// .... 

tx.commit(); 

EDIT: дополнительные деталей управления транзакциями с применением управляемого Entity Менеджерами

ВНИМАНИЕ: образцами коды ниже для образовательное использование. Я набрал их с головы до головы, чтобы помочь объяснить мои очки. & не успели скомпилировать/отладить/протестировать.

Параметр @TransactionManagement по умолчанию для EJB - TransactionManagement.CONTAINER, а параметр по умолчанию @TransactionAttribute для методов EJB - TransactionAttribute.REQUIRED.

Есть четыре перестановки для управления транзакциями:

  • A) EJB с контейнером, управляемые JTA транзакции

    Это предпочтительный подход Java EE.
    EJB class @TransactionManagement аннотация:
    должен автоматически указывать TransactionManagement.CONTAINER или опускать его, чтобы неявно использовать значение по умолчанию.
    EJB метод @TransactionAttribute аннотация: должен быть установлен в TransactionAttribute.REQUIRED явно или опустить его для импликации использовать значение по умолчанию. (Примечание. Если у вас был другой бизнес-сценарий, вы можете использовать TransactionAttribute.MANDATORY или TransactionAttribute.REQUIRES_NEW, если их семантика соответствует вашим потребностям.)
    Менеджеры управления приложениями:
    они должны быть созданы с помощью Persistence.createEntityManagerFactory ("unitName") и emf.createEntityManager(), как описано выше.
    Присоединиться к EntityManager с транзакцией JTA:
    Создайте EntityManager WITHON для транзакционного метода EJB, и они автоматически присоединятся к транзакции JTA. ИЛИ если EntityManagers создаются заранее, вызовите em.joinTransaction() в рамках метода EJB транзакции.
    Вызов EntityManager.close(), когда вы закончите использовать их. Это должно быть все, что требуется.

    Основные примеры - просто использовать больше EntityManagers для сделки по нескольким БД:

    @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 удалось JTA транзакции

    Использование @ TransactionManagement.BEAN.
    Внедрение интерфейса JTA UserTransaction, поэтому компонент может непосредственно отмечать транзакции JTA.
    Ручная маркировка/синхронизация транзакции через UserTransaction.begin()/commit()/rollback().
    Убедитесь, что EntityManager присоединяется к транзакции JTA - либо создайте EM в активном контексте транзакции JTA, либо вызовите em.joinTransaction().

    Примеры:

    @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(); 
        } 
    
    } 
    
  • С) POJO/Non-EJB с ручной закодированы (рожкового управляемые) ресурсов локальных операций (не ССТ)

    Просто использовать интерфейс JPA EntityTransaction для ТХ демаркации (полученный через em.getTransaction()).

    Пример:

    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 с ручной закодированы (POJO управляемого) ССТ сделок

    Это предполагает POJO/компонент работает в каком-то контейнере, который имеет JTA поддержка.
    Если в контейнере Java EE можно использовать вложение ресурсов Java EE интерфейса JTA UserTransaction.
    (. В качестве альтернативы, можно явно LookUp дескриптор интерфейса ССТ и делать демаркацию на нем, а затем вызвать em.getTransaction() joinTransaction() - см JTA спецификации.)

    Пример:

    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

Это определенно помогло. Я создал EMs, как вы предлагаете, и я закрыл их после выполнения задания, хотя мне пришлось аннотировать EJB как '@TransactionManagement (TransactionManagementType.BEAN)', поскольку проблема «одной транзакции JTA» все еще происходила с '@TransactionManagement (@TransactionManagement) TransactionManagementType.CONTAINER) '. Кроме того, я получаю это исключение: org.apache.openjpa.persistence.TransactionRequiredException: может выполнять операцию только во время транзакции. Похоже, мне нужно создать/зафиксировать/откат/закрыть транзакцию вручную. Это верно? –

+0

Рад, что это помогло. ЕСЛИ вы используете @TransactionManagement (TransactionManagementType.BEAN, тогда вам нужно выполнить демаркацию/синхронизацию транзакций JTA: –

+0

... на самом деле - забудьте этот комментарий: я добавил к концу сообщения выше, а не B ^) –

-2

Попытка создать запрос не родной запрос первым, возвращающих список баров. Также старайтесь комментировать инъекцию H2 в вашем EJB. Если это работает, то вы знаете, что это проблема с конфликтом CDI.

+0

Я прокомментировал 'emH2' и его аннотации и сделал только запрос JPQL на' emOracle'. Это сработало, но это не решение. Я не понимаю, как обе единицы сохранения/источники данных/квалификаторы находятся в конфликте, когда они явно ссылаются на разные вещи. –

+0

Будьте уверены, что класс Bar будет в указанном вами пакете. Попробуйте использовать true для обоих PU. – javadev

+0

Пакеты в порядке. Класс ' true' включен в _persistence.xml_, как вы можете видеть. –

Смежные вопросы