在没有XA的事务中是否可以有两个MSSQL持久性单元?

时间:2017-01-26 07:35:27

标签: sql-server jpa java-ee xa persistence-unit

我们有一个应用程序,它有许多实体类,必须有两个表。表格相同,唯一的区别是名称。这里提供的常见解决方案是使用继承(映射的超类和每类表策略)或两个具有不同映射的持久性单元。我们使用后一种解决方案,应用程序建立在这种方法之上,因此它现在被认为是给定的。

有两种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。

1 个答案:

答案 0 :(得分:5)

经过一些实验,我发现事实上可能有两个持久性单元在容器管理的事务中使用非XA资源。但是,它可能依赖于实现。 TL; DR在底部。

如果多个资源参与事务,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>

有两个持久性单元,一个名称为playground,另一个名称为playground-copy。后者有一个ORM映射文件,但除此之外还有一点点。重要的是两者都指定了相同的<jta-data-source>

在应用程序服务器(本例中为GlassFish)中,我们将有一个JDBC连接池,其中一个名为playground的JDBC资源使用此池。

connection pool and resource

现在,如果将两个持久性上下文注入到EJB中,并且调用了一个被认为是在容器管理的事务中的方法,那么您希望事情看起来像这样。

persistence unit connections

两个持久化上下文都使用相同的数据源,但事务管理器和JPA层都不应该真正关心它。毕竟,他们可能有不同的数据源。由于数据源无论如何都由连接池支持,因此您希望两个单元都能获得自己的连接。 XA允许工作以事务方式运行,因为启用XA的资源将实现两阶段提交。

但是,当尝试上面的数据源指向具有非XA实现的连接池(并进行一些实际的持久性工作)时,没有异常,一切正常! MSSQL服务器中的XA支持甚至被禁用,并且尝试使用XA驱动程序会导致错误,直到启用它为止,所以它不像我在不知情的情况下意外使用XA。

使用调试器进入代码时发现,两个持久性上下文(实际上是不同的实体管理器)确实使用了相同的连接。进一步挖掘表明连接未设置为在XA事务中,并且在JDBC级别上具有相同的事务标识符。情况变成了这样:

shared connection

我只能假设如果为同一个事务创建多个单元,JPA提供程序会优化使用相同的连接。那么,为什么这样可以呢?在JDBC级别,事务在连接上提交。据我所知,JDBC规范并没有提供在单个连接上运行多个事务的方法。这意味着如果提交了一个持久化上下文的工作,那么另一个持久化上下文也会发生。

但这实际上是为什么。分布式事务的提交点应该好像所有部分形成一个整体(假设在投票阶段所有投票&#34;是&#34;)。在这种情况下,两个持久化上下文都在同一个连接上运行,因此它们隐含着一个工作单元。由于事务是由容器管理的,因此无论如何都无法立即访问它,这意味着您无法提交一个上下文而不能另一个上下文。并且只有一个实际注册事务的连接,它不必是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实现,版本或应用程序服务器上依赖此行为。所以测试并且不要期望完全可移植性。