我们有一个应用程序,它有许多实体类,必须有两个表.表格相同,唯一的区别是名称.这里提供的常见解决方案是使用继承(映射的超类和每类表策略)或两个具有不同映射的持久性单元.我们使用后一种解决方案,应用程序是建立在这种方法之上的,所以它现在被认为是给定的.
EJB方法将在两个持久性上下文中进行更新,并且必须在一个事务中执行此操作.两个持久性上下文都具有相同的数据源,这是与Microsoft SQL Server数据库(2012版)的启用XA的连接.上下文之间的唯一区别是,有一个映射XML可以更改某些实体类的表名,从而适用于这些表.
其中一个架构主管希望看到XA事务被消除,因为它们会对数据库造成巨大的开销,并且显然也会使执行的查询的日志记录和分析更加困难,可能还会阻止一些预先准备好的语句缓存.我不知道所有的细节,但对于很多应用程序,我们已经成功地消除了XA.然而,对于这一个,我们目前不能因为两个持久化上下文.
在这种情况下是否有某种方法可以在没有XA的情况下以事务方式对两个上下文进行更新?如果是这样,怎么样?如果没有,是否可以使用一个持久化上下文进行架构或配置更改而不必转向两个表的子类?
我知道这些问题:是否可以在事务中使用多个持久性单元,而不是XA?和 两阶段提交的XA事务
在投票将其作为副本关闭之前,请注意情况不同.我们不像第一个问题那样处于只读状态,两个上下文都在同一个数据库上运行,我们只使用MSSQL而我们使用的是GlassFish,而不是Weblogic.
经过一些实验,我发现事实上可能有两个持久性单元在容器管理的事务中使用非XA资源.但是,它可能依赖于实现.TL; DR在底部.
如果多个资源参与事务,JTA应该需要XA资源.它使用X/Open XA来允许分布式事务,例如在多个数据库或数据库和JMS队列上.显然有一些优化(可能是GlassFish特定的,我不确定)允许最后一个参与者是非XA.但是,在我的用例中,两个持久性单元都用于同一个数据库(但是一组不同的表,有一些可能的重叠)并且两者都是非XA.这意味着当第二个资源不支持XA时,我们期望抛出异常.
假设这是我们的persistence.xml
org.hibernate.ejb.HibernatePersistence jdbc/playground org.hibernate.ejb.HibernatePersistence jdbc/playground META-INF/orm-playground-copy.xml
有两个持久性单元,一个具有名称playground
,另一个具有名称playground-copy
.后者有一个ORM映射文件,但除此之外还有一点点.重要的是两者都具有相同的
指定.
在应用程序服务器(本例中为GlassFish)中,我们将有一个JDBC连接池,其JDBC资源名称playground
使用此池.
现在,如果将两个持久性上下文注入到EJB中,并且调用了一个被认为是在容器管理的事务中的方法,那么您希望事情看起来像这样.
两个持久化上下文都使用相同的数据源,但事务管理器和JPA层都不应该真正关心它.毕竟,他们可能有不同的数据源.由于数据源无论如何都由连接池支持,因此您希望两个单元都能获得自己的连接.XA允许工作以事务方式运行,因为启用XA的资源将实现两阶段提交.
但是,当尝试上面的数据源指向具有非XA实现的连接池(并进行一些实际的持久性工作)时,没有异常,一切正常!MSSQL服务器中的XA支持甚至被禁用,并且尝试使用XA驱动程序会导致错误,直到它被启用,因此我不会在不知情的情况下意外使用XA.
使用调试器进入代码后发现,两个持久化上下文,即不同的实体管理器(正如他们应该)实际上使用相同的连接.进一步挖掘表明连接未设置为在XA事务中,并且在JDBC级别上具有相同的事务标识符.所以情况变成了这样:
我只能假设如果为同一事务创建了多个单元,JPA提供程序可以优化使用相同的连接.那么,为什么这样可以呢?在JDBC级别,事务在连接上提交.据我所知,JDBC规范没有提供在单个连接上运行多个事务的方法.这意味着如果提交了一个持久化上下文的工作,则提交也将发生在另一个上.
但这实际上是它的原因.分布式事务的提交点应该好像所有部分形成一个整体(假设在投票阶段所有投票都是"是").在这种情况下,两个持久化上下文都在同一个连接上运行,因此它们隐含着一个工作单元.由于事务由容器管理,因此无论如何都无法立即访问它,这意味着您无法提交一个上下文而不能另一个上下文.并且只有一个实际注册事务的连接,它不必是XA,因为从事务管理器的角度来看,它不被认为是分布式的.
请注意,这不违反持久性上下文的位置.从数据库中获取实体会在两个上下文中生成单独的对象.它们仍然可以彼此独立地运行,就像它们使用单独的连接一样.在上图中,具有相同主键的相同类型的获取实体表示相同的数据库行,但是由它们各自的实体管理器管理的单独对象.
为了验证这确实是JPA提供程序的一些优化,我创建了第二个连接池(到同一个数据库)和一个单独的JDBC资源,为第二个持久性单元设置并测试.这导致预期的异常:
Caused by: java.sql.SQLException: Error in allocating a connection. Cause: java.lang.IllegalStateException: Local transaction already has 1 non-XA Resource: cannot add more resources.
如果您创建了两个JDBC资源,但指向同一个连接池,那么它再次正常工作.这甚至在明确使用类时也有效com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource
,确认它可能是JPA级别的优化,而不是意外地为同一数据源获取两次相同的连接(这会破坏GlassFish池).使用XA数据源时,它确实是一个支持XA的连接,但JPA提供程序仍将对两个持久性上下文使用相同的连接.只有在使用单独的池时,它实际上是两个完全独立的XA连接,并且您将不再获得上述异常.
那么,有什么收获?首先,我没有在JPA或JTA规范中找到任何描述(或强制)此行为的内容.这意味着这可能是特定于实现的优化.转移到其他JPA提供程序,甚至是不同的版本,它可能不再起作用.
其次,它可能会陷入僵局.如果您在两个上下文中获取上面示例中的实体,那么将其更改为一个并刷新,这没关系.在一个上下文中获取它,调用flush方法然后尝试在另一个上下文中获取它,你可能会遇到死锁.如果您允许读取未提交的事务隔离,则可以避免这种情况,但是您在一个上下文中看到的内容取决于您在另一个上下文中获取刷新的时间.所以手动刷新电话可能会很棘手.
作为参考,使用的GlassFish版本是3.1.2.2
.JPA提供程序是Hibernate版本3.6.4.Final
.
TL; DR
是的,您可以在JavaEE容器管理的事务中使用具有相同非XA资源的两个持久性上下文,并保留ACID属性.但是,这要归功于在为具有相同数据源的同一事务创建多个EntityManager时,Hibernate优化的可能性.由于JPA或JTA规范似乎没有强制要求,因此您可能不会在JPA实现,版本或应用程序服务器上依赖此行为.所以测试并不要期望完全可移植性.