2017-01-26 112 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或類似的)的請求,並通過分析器檢查內存消耗是否在增長。 –

相關問題