2017-01-26 41 views
10

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

有一些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。

+0

如果这两个持久性单元使用相同的数据源,那么你不应该需要XA –

+0

@SteveC它们将从一个池中获得它们自己的连接,所以我没有看到强迫它们具有相同连接的任何方式(以及因此交易)或JPA如何实际管理这一点。 –

+0

我认为他们都会参加同一个JTA交易...... –

回答

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规范没有提供在单个连接上运行多个事务的方法。这意味着如果一个持久化上下文的工作被提交,那么提交也会发生在另一个上。

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

+1

您确定两个实体管理器的持久化上下文(会话)是否已正确关闭?如果没有,如果持久化上下文实例在后续请求中被重用,最终可能会在一段时间后没有内存,或者处理陈旧的数据。 –

+0

@DraganBozanovic好点。我认为(可能是错误的),通过努力,Hibernate开发人员必须明确检测两个上下文是否在同一个容器事务中并让它们共享连接,这已经考虑在内。会有一些很好的方法来测试它吗?一旦离开一个调用,我就不在事务上下文中,所以我没有什么可调试的。我想我可以获取Hibernate源代码 –

+0

一开始,您可以发起大量涉及实体管理器(通过jmeter或类似的)的请求,并通过分析器检查内存消耗是否在增长。 –

相关问题