有一些EJB方法将对两个持久性上下文进行更新,并且必须在一个事务中执行更新.两个持久化上下文都具有相同的数据源,这是与Microsoft sql Server数据库(2012版)的XA启用连接.上下文之间的唯一区别是,有一个映射XML可以改变一些实体类的表名,从而对这些表进行工作.
架构领导者之一希望看到XA交易被淘汰,因为它们对数据库造成了重大的开销,显然也使执行的查询的日志记录和分析更加困难,也可能阻止一些准备好的语句缓存.我不知道所有的细节,但是我们设法消除XA的很多应用程序.然而,对于这一个,我们目前不能因为两个持久性上下文.
在这种情况下,是否有一些方法可以在没有XA的情况下以两种上下文方式获得更新呢?如果是这样,怎么办?如果没有,是否有一些架构或配置更改可能使用一个持久化上下文而不必转向两个表的子类?
我知道这些问题:Is it possible to use more than one persistence unit in a transaction,without it being XA?和
XA transaction for two phase commit
在投票结束之前,将其作为重复,请注意情况不同.我们不像第一个问题那样处于只读状态,两个上下文都在同一个数据库上运行,我们正在使用MSsql,而是使用GlassFish,而不是Weblogic.
解决方法
如果多个资源参与交易,JTA应该需要XA资源.它使用X / Open XA来实现分布式事务,例如通过多个数据库或数据库和JMS队列.显然有一些优化(可能是GlassFish特定的,我不确定),允许最后一个参与者是非XA.然而,在我的用例中,两个持久性单元都是相同的数据库(但是一组不同的表,有一些可能的重叠),而且都是非XA.这意味着我们期望在第二个资源不支持XA时抛出异常.
假设这是我们的persistence.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="playground" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>jdbc/playground</jta-data-source> <properties> <property name="hibernate.dialect" value="be.dkv.hibernate.sqlServer2012Dialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="true" /> </properties> </persistence-unit> <persistence-unit name="playground-copy" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>jdbc/playground</jta-data-source> <mapping-file>Meta-INF/orm-playground-copy.xml</mapping-file> <properties> <property name="hibernate.dialect" value="be.dkv.hibernate.sqlServer2012Dialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="true" /> </properties> </persistence-unit> </persistence>
有两个持续单位,一个是名字操场,另一个名称是操场复制.后者有一个ORM映射文件,但这是一个点以外.重要的是两者都具有相同的< jta-data-source>指定.
在应用程序服务器(在这种情况下为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属性.然而,这是由于当使用相同的数据源为同一个事务创建多个EntityManagers时,Hibernate的优化可能是可能的.由于JPA或JTA规范似乎不受任何授权,因此您可能不会依赖JPA实现,版本或应用程序服务器上的此行为.所以测试并不期望完全可移植性.