为啥不同数据源的持久化单元查询同一个数据源?
Posted
技术标签:
【中文标题】为啥不同数据源的持久化单元查询同一个数据源?【英文标题】:Why different persistence units with separated data sources query the same data source?为什么不同数据源的持久化单元查询同一个数据源? 【发布时间】:2013-02-23 02:57:26 【问题描述】:我正在开发一个需要访问两个不同数据库服务器(H2 和 Oracle)的 web 应用程序。 该容器是一个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>
【问题讨论】:
只是复制粘贴错误,还是您的@PercistenceContext
中有错误的单位名称?应该是FenixRadarPU
?
复制粘贴错误。我已经更正了。谢谢!
将@PersistenceContext(unitName = "...")
注释直接添加到Service
类中的EntityManager
s 以尝试确定这是CDI 问题还是JPA 问题。
@RichardSitze 我最近尝试过,结果是相同的,将实体管理器的声明更改为:@PersistenceContext(unitName="OraclePU") private EntityManager emOracle; @PersistenceContext(unitName="H2PU") private EntityManager emH2;
只是一个注释,以确保 beans.xml 没有任何配置设置 - 将覆盖代码中的 CDI 注释。我看到这没有发生 - @PC(unitName=...) 测试上面的两点绕过 CDI。
【参考方案1】:
容器管理的持久性上下文
当使用容器管理的持久性上下文时(就像您通过 @PersistenceContext 注释一样),JPA 规范指定只有一个持久性上下文可以与 JTA 事务相关联。
持久性上下文由 Java EE 容器创建。尽管出现了代码(@PersistenceContext 注释似乎表明 PC 被直接注入到您的 EntityManager 实例变量中),但持久性上下文实际上存储为 JTA 事务中的引用。每次 EntityManager 操作发生时,它都不会引用它自己的内部持久性上下文。相反,它执行一个特殊操作,因为它是容器管理的——它总是在 JTA 事务中查找持久性上下文并使用它。这称为 JTA 持久性上下文传播。
来自 JPA 规范的一些引用:
当使用容器管理的实体管理器时, 持久性上下文总是自动管理,透明地 应用程序,并且持久性上下文通过 JTA 交易。
容器管理的事务范围持久化上下文
... 一个新的持久化上下文开始于容器管理的实体管理器 在活动 JTA 事务的范围内被调用[76],并且有 当前没有与 JTA 关联的持久性上下文 交易。创建持久化上下文,然后关联 与 JTA 交易。
容器管理的扩展持久化上下文
... 容器管理的扩展持久化上下文只能在作用域内启动 有状态会话 bean。它从有状态会话 bean 的位置开始存在 声明对 PersistenceContextType.EXTENDED 类型的实体管理器的依赖 被创建,并且被称为绑定到有状态会话 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() - 最好通过最终的 子句或通过 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 事务加入 EntityManager: 在事务性 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/非 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
// ...
// ...
【讨论】:
这绝对有帮助。我按照您的建议创建了 EM,并在工作完成后关闭了它们,尽管我不得不将 EJB 注释为@TransactionManagement(TransactionManagementType.BEAN)
,因为 @TransactionManagement(TransactionManagementType.CONTAINER)
仍然存在“单个 JTA 事务”问题。此外,我现在收到此异常org.apache.openjpa.persistence.TransactionRequiredException: Can only perform operation while a transaction is active
。好像我需要手动创建/提交/回滚/关闭事务。这是正确的吗?
很高兴它有帮助。如果你使用@TransactionManagement(TransactionManagementType.BEAN,那么你需要做你自己的JTA事务划分/同步:
... 实际上 - 忘记那条评论:我已经附加到上面帖子的末尾而不是 B^)
总结:(1)您应该能够将 TransactionManagementType.CONTAINER 与应用程序管理的实体管理器一起使用。它们应该加入容器的 JTA 事务,而无需与容器/JTA 传递/共享它们的持久性上下文——这意味着容器应该允许两个 EM 针对两个不同的数据库独立操作。 (2) 如果你选择使用TransactionManagement.BEAN,那么是的,你必须手动控制JTA事务——注入JTA接口为:@Resource UserTransaction jtaTx;确保 EM 加入交易:em.joinTransaction()。
老兄...谢谢你。我一直在试图弄清楚如何进行跨越多个 bean/pojo 调用(来自 REST 服务环境)的事务,我很确定这会对我有所帮助。【参考方案2】:
首先尝试创建一个查询而不是本机查询,返回一个条形列表。 还尝试在您的 EJB 中注释 H2 注入。如果它有效,那么您就知道这是一个 CDI 冲突问题。
【讨论】:
我评论了emH2
及其注释,并且只对emOracle
进行了JPQL 查询。它有效,但这不是解决方案。当持久性单元/数据源/限定符明确引用不同的事物时,我不明白它们是如何发生冲突的。
确保 Bar 类位于您列出的包中。试试两个 PU 的 <exclude-unlisted-classes>true</exclude-unlisted-classes>
包含在 persistence.xml 中。以上是关于为啥不同数据源的持久化单元查询同一个数据源?的主要内容,如果未能解决你的问题,请参考以下文章
使用Spring时如何注入多个JPA EntityManager(持久化单元)
当persistence.xml存在时,为啥持久性单元命名为null