为什么具有分离数据源的不同持久性单元会查询相同的数据源?

我正在开发一个需要访问两个不同数据库服务器(H2和Oracle)的webapp。 容器是Apache Tomee 1.5.1 ,我使用Java EE堆栈,其中包含库(JSF,JPA,CDI,EJB等)。

我正在尝试在XA事务中使用两个实体管理器从Oracle数据库中提取数据并在转换后将其保留在H2中,但无论我使用的实体管理器如何,所有查询都是针对H2数据库执行的。 有帮助吗?

编辑 :我发现,如果我尝试以相反的顺序访问实体管理器,它们的行为是相同的,但访问Oracle。 即:实体经理留在第一个访问的数据库。

发生这种情况的EJB service.getFoo()从JSF调用service.getFoo() ):

 @Named @Stateless public class Service { @Inject @OracleDatabase private EntityManager emOracle; @Inject @H2Database private EntityManager emH2; @TransactionAttribute(TransactionAttributeType.REQUIRED) public List getFoo() { TypedQuery q = emH2.createQuery( "SELECT x FROM Foo f", Foo.class); List 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 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; } 

我的peristence.xml看起来像这样:

    org.apache.openjpa.persistence.PersistenceProviderImpl H2DS my.app.h2.Foo true   org.apache.openjpa.persistence.PersistenceProviderImpl OracleDS my.app.oracle.Bar true   

最后, tomee.xml中的数据源(此文件中没有配置任何其他数据源):

  jdbcDriver = oracle.jdbc.xa.client.OracleXADataSource jdbcUrl = jdbc:oracle:thin:@server:port:instance jtaManaged = true password = abcde userName = user   jdbcDriver=org.h2.jdbcx.JdbcDataSource jdbcUrl=jdbc:h2:h2/db;AUTO_SERVER=TRUE jtaManaged = true password = edcba userName = user  

容器管理持久性上下文

使用容器管理的持久性上下文时(因为您通过@PersistenceContext注释),JPA规范指定只有一个持久性上下文可能与JTA事务关联。

持久性上下文由Java EE容器创建。 尽管出现了代码(@PersistenceContext注释似乎表明PC直接注入到您的EntityManager实例变量中),但持久化上下文实际上存储为JTA TRANSACTION中的引用。 每次执行EntityManager操作时,它都不会引用它自己的内部持久性上下文。 相反,它执行特殊操作,因为它是容器管理的 – 它总是查找JTA事务中的持久性上下文并使用它。 这称为JTA持久性上下文传播。

JPA规范中的一些行情:

使用容器管理的实体管理器时,始终自动管理持久性上下文的生命周期,对应用程序透明,并使用JTA事务传播持久性上下文。

容器管理的事务范围持久化上下文

当在活动JTA事务的范围内调用容器管理的实体管理器[76]时,新的持久化上下文开始,并且当前没有与JTA事务关联的持久性上下文。 创建持久性上下文,然后与JTA事务关联。

容器管理的扩展持久化上下文

…容器管理的扩展持久化上下文只能在有状态会话bean的范围内启动。 它存在于创建声明依赖于类型PersistenceContextType.EXTENDED的实体管理器的有状态会话bean的位置,并且被称为绑定到有状态会话bean。 通过PersistenceContext批注或persistence-context-ref部署描述符元素声明对扩展持久性上下文的依赖性。 当有状态会话bean的@Remove方法完成时(或者有状态会话bean实例被破坏),容器将关闭持久性上下文。

持久化上下文传播的要求

…如果调用组件且没有JTA事务…,则不传播持久性上下文。 •调用使用PersistenceContext- Type.TRANSACTION定义的实体管理器将导致使用新的持久性上下文。 •调用使用PersistenceContext-Type.EXTENDED定义的实体管理器将导致使用绑定到该组件的现有扩展持久性上下文。

…如果调用组件并将JTA事务传播到该组件中:•如果组件是已绑定扩展持久性上下文的有状态会话Bean,并且存在绑定到JTA事务的不同持久性上下文,容器抛出EJBException。 •否则,如果存在绑定到JTA事务的持久性上下文,则会传播和使用该持久性上下文。

这就是你的问题。 明显的64美元问题:为什么规范要求这个?

嗯,这是因为它是一种刻意的权衡,它为EJB带来了强大的EntityManager魔力。

使用JTA事务传播单个持久性上下文有一个限制:事务不能跨越多个持久性上下文,因此不能跨越多个数据库。

但是,它也有一个巨大的优势:在EJB中声明的任何entityManager都可以自动共享相同的持久化上下文,因此可以在同一组JPA实体上运行并参与同一事务。 您可以让一系列EJB调用任何复杂的其他EJB,并且它们对JPA实体数据的行为都是合理且一致的。 并且它们也不需要跨方法调用一致初始化/共享实体管理器引用的复杂性 – 可以在每个方法中私有地声明EntityManagers。 实现逻辑可以非常简单。

您的问题的答案:使用应用程序管理的持久性上下文(通过应用程序管理的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后,必须调用em.close() – 优选通过final {}子句或Java 7 try-with-resources语句。

应用程序管理的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(); 

或者甚至更简单,如果在JTA事务的上下文中创建EntityManager,则应用程序管理的EntityManager自动加入JTA事务隐含 – 不需要joinTransaction()。

因此,应用程序管理的EM可以拥有跨越多个数据库的JTA事务。 当然,您可以独立于JTA运行本地资源JDBC事务:

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

编辑:使用应用程序管理的实体管理器进行事务管理的额外详细信息

警告:下面的代码示例仅供教育使用 – 我已经将它们排除在头顶以帮助解释我的观点并且没有时间编译/调试/测试。

EJB的默认@TransactionManagement参数是TransactionManagement.CONTAINER,EJB方法的默认@TransactionAttribute参数是TransactionAttribute.REQUIRED。

交易管理有四种排列方式:

  • A)具有CONTAINER管理的JTA事务的EJB

    这是首选的Java EE方法。
    EJB类@TransactionManagement注释:
    必须显式设置为TransactionManagement.CONTAINER或省略它以隐式使用默认值。
    EJB方法@TransactionAttribute注释:必须显式设置为TransactionAttribute.REQUIRED,或者省略它以隐含使用默认值。 (注意:如果您有不同的业务场景,则可以使用TransactionAttribute.MANDATORY或TransactionAttribute.REQUIRES_NEW,如果它们的语义符合您的需求。)
    应用程序管理的实体经理:
    它们必须通过Persistence.createEntityManagerFactory(“unitName”)和emf.createEntityManager()创建,如上所述。
    使用JTA事务加入EntityManagers:
    在事务EJB方法中创建EntityManagers,它们将自动加入JTA事务。 或者,如果事先创建了EntityManagers,则在事务EJB方法中调用em.joinTransaction()。
    完成使用后调用EntityManager.close()。 这应该是所有需要的。

    基本示例 – 只需使用更多EntityManagers来跨多个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)具有BEAN管理的JTA事务的EJB

    使用@TransactionManagement.BEAN。
    注入JTA UserTransaction接口,因此bean可以直接标记JTA事务。
    通过UserTransaction.begin()/ commit()/ rollback()手动标记/同步事务。
    确保EntityManager加入JTA事务 – 在活动的JTA事务上下文中创建EM或调用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 /非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管理)JTA事务的POJO /非EJB

    这假设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 // ... } // ... } } 

首先尝试创建Query而不是本机查询,返回Bars列表。 还尝试在EJB中注释H2注入。 如果它有效,那么你知道这是一个CDI冲突问题。