当前位置: 首页 > 面试题库 >

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

欧阳俊晖
2023-03-14
问题内容

我正在开发一个Webapp,它需要访问两个不同的数据库服务器(H2和Oracle)。容器是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...
    }
}

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

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已直接注入您的EntityManager实例变量中),但持久性上下文实际上是作为引用存储在JTA事务中的。每次执行EntityManager操作时,它都不会引用它自己的内部持久性上下文。相反,它进行了特殊的操作,因为它是容器管理的-
它总是在JTA Transaction中查找持久性上下文并使用它。这称为JTA持久性上下文传播。

JPA规范的一些引言:

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

容器管理的事务作用域持久性上下文

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

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

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

持久性上下文传播的要求

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

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

那就是你的问题。 一个明显的$ 64问题:为什么规范要求这样做?

嗯,这是因为这是一个有意的折衷,将强大的EntityManager魔术带给EJB。

使用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
相关联或传播到任何 容器管理的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即可在多个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 / 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管理的)JTA事务的POJO / Non-EJB

假设POJO /组件在具有JTA支持的某个容器中运行。
如果在Java EE容器中,可以使用JTA UserTransaction接口的Java EE资源注入。
(或者,可以显式地查找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
           // ...
        }
    // ...
    }
}


 类似资料:
  • 我有一个有两个数据源的项目,一个主数据源和一个备用数据源。我已将这些添加到应用程序中。属性,我正在使用数据库多租户方法。我有一个定制的TenantResolver实现,可以更改活动租户,我定义的唯一实体是在外部模块中。 问题很简单:保存到我的两个数据源工作正常,但是为了启动应用程序,我必须包含一个虚拟数据源: 在属性文件中。否则我会得到错误:默认数据源未配置,但持久化单元“”使用它。我使用的实际数

  • 问题内容: 我正在使用ajax加载数据并在DataTable中动态生成列名。我的数据表具有不同的列数,具体取决于用户的选择。(有一个下拉列表)。 例如,下拉列表中有2个选项,即“ 南部省” 和“ 北部省” 。 南部省 表有4列, 北部省 表有6列。 场景1 第一个用户选择具有4列的 Southern Province 。然后它生成的表没有错误,但是在此之后,如果用户选择具有6列的 Northern

  • 问题内容: 我需要一些帮助,以使用同一持久性单元配置多个数据库的多个连接。 它们都具有相同的架构。因此,我想使用相同的持久性单元/ DAO等,而又不想设置10个EntityManager,10个持久性xml等。有没有办法做到这一点?这是我当前的配置: 我也在使用Spring / hibernate来设置我的上下文: 最后我使用: 将我的EntityManager注入我的DAO 如何扩展此模型以能够

  • 有人成功地在Spring Boot应用程序中配置了两个具有不同数据源的hikari连接池吗?如何使用application.properties来完成?

  • 问题内容: 我需要一些帮助,以使用相同的持久性单元配置多个数据库的多个连接。 它们都具有相同的架构。因此,我想使用相同的持久性单元/ DAO等,而又不想设置10个EntityManager,10个持久性xml等。有没有办法做到这一点?这是我当前的配置: 我也在使用Spring / hibernate来设置我的上下文: 最后我使用: 将我的EntityManager注入我的DAO 如何扩展此模型以能

  • 我正在使用Microsoft Graph Users API根据我们的ActiveDirectory验证用户名或电子邮件地址列表。名称搜索: https://graph.microsoft.com/v1.0/me/people/?$search=John.Smith 返回其他数据,如和。但如果我使用电子邮件搜索: null 我是否需要额外的权限来获取相同的数据? 更新:我按照下面的建议在https