Ответ 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 // ... } // ... } }