Подтвердить что ты не робот

Почему разные единицы сохранения с отдельными источниками данных запрашивают один и тот же источник данных?

Я разрабатываю 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 являются квалификаторами):

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>
4b9b3361

Ответ 1

Контекст с управляемым контейнером

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

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

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

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

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

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

Контекстно-зависимый контекст с сохранением контейнера

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

Требования к распространению контекста устойчивости

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

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

Так что ваша проблема. Очевидный вопрос в $64: ПОЧЕМУ спецификация запрашивает это?

Ну, это потому, что это преднамеренный компромисс, который приносит мощные магии 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 - предпочтительнее через окончательное предложение {} или с помощью инструкции try-with-resources от Java 7.

Управляемые EM-приложения по-прежнему участвуют в транзакциях 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();

РЕДАКТИРОВАТЬ: ДОПОЛНИТЕЛЬНЫЕ ДЕТАЛИ для управления транзакциями с менеджерами управления, управляемыми приложениями

ПРЕДУПРЕЖДЕНИЕ: примеры кода, приведенные ниже, предназначены для использования в образовательных целях. Я набрал их с головы до головы, чтобы объяснить мои вопросы и не успел скомпилировать/отладить/протестировать.

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

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

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

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

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

    @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, поэтому bean может напрямую отмечать транзакции 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();
        }
    
    }
    
  • C) POJO/Non-EJB с ручными (bean управляемыми) ресурсами локальными транзакциями (не JTA)

    Просто используйте интерфейс JPA EntityTransaction для демаркации tx (полученный через 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 с транзакциями JTA с ручным управлением (POJO)

    Это предполагает, что POJO/компонент работает в некотором контейнере с поддержкой JTA.
    Если в контейнере Java EE можно использовать вложение ресурсов Java EE интерфейса JTA UserTransaction.
    (В качестве альтернативы можно явно просмотреть дескриптор интерфейса JTA и выполнить демаркацию на нем, а затем вызвать 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
               // ...
            }
        // ...
        }
    }
    

Ответ 2

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