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

标签 java jakarta-ee jpa cdi jta

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

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

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

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

@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;
}

我的 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>

最佳答案

容器管理的持久性上下文

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

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

JPA 规范中的一些引用:

When a container-managed entity manager is used, the lifecycle of the persistence context is always managed automatically, transparently to the application, and the persistence context is propagated with the JTA transaction.

Container-managed Transaction-scoped Persistence Context

... A new persistence context begins when the container-managed entity manager is invoked[76] in the scope of an active JTA transaction, and there is no current persistence context already associated with the JTA transaction. The persistence context is created and then associated with the JTA transaction.

Container-managed Extended Persistence Context

... A container-managed extended persistence context can only be initiated within the scope of a stateful session bean. It exists from the point at which the stateful session bean that declares a dependency on an entity manager of type PersistenceContextType.EXTENDED is created, and is said to be bound to the stateful session bean. The dependency on the extended persistence context is declared by means of the PersistenceContext annotation or persistence-context-ref deployment descriptor element. The persistence context is closed by the container when the @Remove method of the stateful session bean completes (or the stateful session bean instance is otherwise destroyed).

Requirements for Persistence Context Propagation

... If a component is called and there is no JTA transaction ..., the persistence context is not propagated. • Invocation of an entity manager defined with PersistenceContext- Type.TRANSACTION will result in use of a new persistence context. • Invocation of an entity manager defined with PersistenceContext- Type.EXTENDED will result in the use of the existing extended persistence context bound to that component.

... If a component is called and the JTA transaction is propagated into that component: • If the component is a stateful session bean to which an extended persistence context has been bound and there is a different persistence context bound to the JTA transaction, an EJBException is thrown by the container. • Otherwise, if there is a persistence context bound to the JTA transaction, that persistence context is propagated and used.



所以这就是你的问题。
显而易见的 64 美元问题:为什么规范要求这个???


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

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

但是,它也有一个巨大的优势:在 EJB 中声明的任何 entityManager 都可以自动共享相同的持久化上下文,因此可以对相同的 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 后,您必须调用 em.close() - 最好通过最后的 { } 子句或通过 Java 7 try-with-resources 语句。

应用程序管理的 EM 仍然参与(即同步)JTA 事务。任何数量的应用程序管理的 EM 都可以参与单个 JTA 事务——但这些事务都不会将它们的持久性上下文与任何容器管理的 EM 关联或传播到任何容器管理的 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 事务。当然,您始终可以运行独立于 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 方法中创建 EntityManager,它们将自动加入 JTA 事务。或者,如果预先创建了 EntityManager,则在事务 EJB 方法中调用 em.joinTransaction()。
    使用完后调用 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) 带有 BEAN 管理的 JTA 事务的 EJB

    使用@TransactionManagement.BEAN。
    注入(inject) JTA UserTransaction 接口(interface),这样 bean 就可以直接标记 JTA 事务。
    通过 UserTransaction.begin()/commit()/rollback() 手动标记/同步事务。
    确保 EntityManager 加入 JTA 事务 - 在 Activity 的 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) 带有手动编码(bean 管理)资源本地事务(不是 JTA)的 POJO/非 EJB

    只需使用 JPA EntityTransaction 接口(interface)进行 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 容器中,可以使用 JTA UserTransaction 接口(interface)的 Java EE 资源注入(inject)。
    (或者,可以显式查找 JTA 接口(interface)的句柄并对其进行划分,然后调用 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
               // ...
            }
        // ...
        }
    }
    
  • 关于java - 为什么具有分离数据源的不同持久化单元查询相同的数据源?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15279184/

    相关文章:

    design-patterns - 将 SQL View 映射到 JPA 实体

    java - hibernate 嵌入 : component property not found

    java - JPA 注释 - 如何从与当前对象不同的表中检索单个值?

    java - 将 JPA AttributeConverter 用于 boolean Y/N 字段 : "Unable to render boolean literal value"

    java - FileReader 在 Java 中不工作

    java - 仅获取 Criteria 查询中的实体 ID

    java - 如何以编程方式计算 Android 上的实时移动数据速度?

    java - Hadoop : Tools for visualizing key value data and files for development

    jakarta-ee - 如何让 weblogic 从内部文件中获取登录模块 jaas.config?

    jakarta-ee - org.codehaus.classworlds.NoSuchRealmException : plexus. 核心