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

Alb*_*ura 24 java jpa jta java-ee cdi

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

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

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

发生这种情况的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...
    }
}
Run Code Online (Sandbox Code Playgroud)

实体管理器的资源生成器(CDI)(其中@ H2Database和@OracleDatabase是限定符):

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

    @Produces
    @PersistenceContext(unitName = "H2PU")
    @H2Database
    private EntityManager emH2;
}
Run Code Online (Sandbox Code Playgroud)

我的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>
Run Code Online (Sandbox Code Playgroud)

最后,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>
Run Code Online (Sandbox Code Playgroud)

Gle*_*est 45

容器管理持久性上下文

使用容器管理的持久性上下文时(因为您通过@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();
Run Code Online (Sandbox Code Playgroud)

要么

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

and the same for emfOracle & emOracle.    
Run Code Online (Sandbox Code Playgroud)

完成每个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();  
Run Code Online (Sandbox Code Playgroud)

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

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

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

// ....

tx.commit();
Run Code Online (Sandbox Code Playgroud)

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

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

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进行事务处理:

    @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();
        }
    // ...
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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();
        }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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
               // ...
            }
        // ...
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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
               // ...
            }
        // ...
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • 总结:(1)您应该能够将TransactionManagementType.CONTAINER与应用程序管理的实体管理器一起使用.它们应该加入容器的JTA事务,而不必通过容器/ JTA传递/共享它们的持久化上下文 - 这意味着容器应该允许两个EM独立地对两个不同的数据库进行操作.(2)如果您选择使用TransactionManagement.BEAN,则是,您必须手动控制JTA事务 - 将JTA接口注入:@Resource UserTransaction jtaTx; 确保EM加入事务:em.joinTransaction(). (2认同)