上一篇文章介紹了JDBC事務,JDBC可以處理單資料來源的事務,滿足大部分交易處理的需求,但是JDBC事務不能解決多資料來源和分散式交易問題,Java平台給我們提供瞭解決方案--JTA。本文將探討JTA的一些細節。
一 分散式交易
通常把一個資料庫內部的交易處理,如對多個表的操作,作為本地事務看待。資料庫和JDBC的交易處理對象是本地事務,而分散式交易處理的對象是全域事務。
所謂全域事務,是指分散式交易處理環境中,多個資料庫可能需要共同完成一個工作,這個工作即是一個全域事務,例如,一個事務中可能更新幾個不同的資料庫。對資料庫的操作發生在系統的各處,但必須全部被提交或復原。此時一個資料庫對自己內部所做操作的提交不僅依賴本身操作是否成功,還要依賴與全域事務相關的其它資料庫的操作是否成功,如果任一資料庫的任一操作失敗,則參與此事務的所有資料庫所做的所有操作都必須復原。
一般情況下,某一資料庫無法知道其它資料庫在做什麼,因此,在一個 DTP 環境中,交易中介軟體是必需的,由它通知和協調相關資料庫的提交或復原。而一個資料庫只將其自己所做的操作(可恢複)影射到全域事務中,這個環境就是分散式交易處理模型。
二 XA規範
X/Open 組織(即現在的 Open Group )定義了分散式交易處理模型。 X/Open DTP 模型( 1994 )包括應用程式( AP )、交易管理員( TM )、資源管理員( RM )、通訊資源管理員( CRM )四部分。
一般常見的交易管理員( TM )是交易中介軟體,例如JDBC或者hibernate提供的transactionmanager,常見的資源管理員( RM )是資料庫,通常就是資料來源,例如JDBC或第三方提供的datasource,常見的通訊資源管理員( CRM )是訊息中介軟體,如JMS。
典型的DTP模型如下:
三 JTA實現
我們還是以前文提到的轉賬為例,說明JTA的具體實現。
<span style="font-size:18px;"> public void transferAccount() { UserTransaction userTx = null; Connection connA = null; Statement stmtA = null; Connection connB = null; Statement stmtB = null; try{ // 獲得 Transaction 管理對象 userTx = (UserTransaction)getContext().lookup("\ java:comp/UserTransaction"); // 從資料庫 A 中取得資料庫連接 connA = getDataSourceA().getConnection(); // 從資料庫 B 中取得資料庫連接 connB = getDataSourceB().getConnection(); // 啟動事務 userTx.begin(); // 將 A 賬戶中的金額減少 500 stmtA = connA.createStatement(); stmtA.execute(" update t_account set amount = amount - 500 where account_id = 'A'"); // 將 B 賬戶中的金額增加 500 stmtB = connB.createStatement(); stmtB.execute("\ update t_account set amount = amount + 500 where account_id = 'B'"); // 提交事務 userTx.commit(); // 事務提交:轉賬的兩步操作同時成功(資料庫 A 和資料庫 B 中的資料被同時更新) } catch(SQLException sqle){ try{ // 發生異常,復原在本事務中的操縱 userTx.rollback(); // 交易回復:轉賬的兩步操作完全撤銷 //( 資料庫 A 和資料庫 B 中的資料更新被同時撤銷) stmt.close(); conn.close(); ... }catch(Exception ignore){ } sqle.printStackTrace(); } catch(Exception ne){ e.printStackTrace(); } }</span>
注意:我們在上述樣本中,使用了兩個資料庫,即connA 和 connB 是來自不同資料庫的串連。
四 JTA實現原理
JTA 究竟是通過何種方式來實現交易管理員呢。 要理解 JTA 的實現原理首先需要瞭解其架構:它包括交易管理員(Transaction Manager)和一個或多個支援 XA 協議的資源管理員 ( Resource Manager ) 兩部分, 我們可以將資源管理員看做任意類型的持久化資料存放區;交易管理員則承擔著所有事務參與單元的協調與控制。 根據所物件導向的不同,我們可以將 JTA 的交易管理員和資源管理員理解為兩個方面:面向開發人員的使用介面(交易管理員)和面向服務提供者的實現介面(資源管理員)。其中開發介面的主要部分即為上述樣本中引用的 UserTransaction 對象,開發人員通過此介面在資訊系統中實現分散式交易;而實現介面則用來規範供應商(如資料庫連接供應商)所提供的事務服務,它約定了事務的資源管理功能,使得 JTA 可以在異構事務資源之間執行協同溝通。以資料庫為例,IBM 公司提供了實現分散式交易的資料庫驅動程式,Oracle 也提供了實現分散式交易的資料庫驅動程式, 在同時使用 DB2 和 Oracle 兩種資料庫連接時, JTA 即可以根據約定的介面協調者兩種事務資源從而實現分散式交易。正是基於統一規範的不同實現使得 JTA 可以協調與控制不同資料庫或者 JMS 廠商的事務資源,其架構如下圖所示:
開發人員使用開發人員介面,實現應用程式對全域事務的支援;各供應商(資料庫,JMS 等)依據供應商介面的規範提供事務資源管理功能;交易管理員( TransactionManager )將應用對分散式交易的使用映射到實際的事務資源並在事務資源間進行協調與控制。 下面,本文將對包括 UserTransaction、Transaction 和 TransactionManager 在內的三個主要介面以及其定義的方法進行介紹。
面向開發人員的介面為 UserTransaction (使用方法如上例所示),開發人員通常只使用此介面實現 JTA 交易管理,其定義了如下的方法:
begin()- 開始一個分散式交易
commit()- 提交事務
rollback()- 復原事務
getStatus()- 返回關聯到當前線程的分散式交易的狀態
setRollbackOnly()- 標識關聯到當前線程的分散式交易將被復原
面向供應商的實現介面主要涉及到 TransactionManager 和 Transaction 兩個對象
Transaction 代表了一個物理意義上的事務,在開發人員調用 UserTransaction.begin() 方法時 TransactionManager 會建立一個 Transaction 事務對象(標誌著事務的開始)並把此對象通過 ThreadLocale 關聯到當前線程。UserTransaction 介面中的 commit()、rollback(),getStatus() 等方法都將最終委託給 Transaction 類的對應方法執行。
registerSynchronization(Synchronization sync)- 回調介面,Hibernate 等 ORM 工具都有自己的事務控制機制來保證事務, 但同時它們還需要一種回調機制以便在事務完成時得到通知從而觸發一些處理工作,如清除緩衝等。這就涉及到了 Transaction 的回調介面 registerSynchronization。工具可以通過此介面將回調程式注入到事務中,當事務成功提交後,回調程式將被啟用。 TransactionManager 本身並不承擔實際的交易處理功能,它更多的是充當使用者介面和實現介面之間的橋樑。下面列出了 TransactionManager 中定義的方法,可以看到此介面中的大部分事務方法與 UserTransaction 和 Transaction 相同。 在開發人員調用 UserTransaction.begin() 方法時 TransactionManager 會建立一個 Transaction 事務對象(標誌著事務的開始)並把此對象通過 ThreadLocale 關聯到當前線程上;同樣 UserTransaction.commit() 會調用 TransactionManager.commit(), 方法將從當前線程下取出事務對象 Transaction 並把此對象所代表的事務提交, 即調用 Transaction.commit()
下面將通過具體的代碼向讀者介紹 JTA 實現原理。下圖列出了樣本實現中涉及到的 Java 類,其中UserTransactionImpl 實現了 UserTransaction 介面,TransactionManagerImpl 實現了 TransactionManager 介面,TransactionImpl 實現了 Transaction 介面
JTA實作類別圖如下:
為什麼必須從支援事務的資料來源中獲得的資料庫連接才支援分散式交易呢。其實支援事務的資料來源與普通的資料來源是不同的,它實現了額外的 XADataSource 介面。我們可以簡單的將 XADataSource 理解為普通的資料來源(繼承了 java.sql.PooledConnection),只是它為支援分散式交易而增加了 getXAResource 方法。另外,由 XADataSource 返回的資料庫連接與普通串連也是不同的,此串連除了實現 java.sql.Connection 定義的所有功能之外還實現了 XAConnection 介面。我們可以把 XAConnection 理解為普通的資料庫連接,它支援所有 JDBC 規範的資料庫操作,不同之處在於 XAConnection 增加了對分散式交易的支援。通過下面的類圖讀者可以對這幾個介面的關係有所瞭解:
應用程式從支援分散式交易的資料來源獲得的資料庫連接是 XAConnection 介面的實現,而由此資料庫連接建立的會話(Statement)也為了支援分散式交易而增加了功能,如下代碼所示:
JTA事務資源處理
<span style="font-size:18px;">public void transferAccount() { UserTransaction userTx = null; Connection conn = null; Statement stmt = null; try{ // 獲得 Transaction 管理對象 userTx = (UserTransaction)getContext().lookup(" java:comp/UserTransaction"); // 從資料庫中取得資料庫連接, getDataSourceB 返回支援分散式交易的資料來源 conn = getDataSourceB().getConnection(); // 會話 stmt 已經為支援分散式交易進行了功能增強 stmt = conn.createStatement(); // 啟動事務 userTx.begin(); stmt.execute("update t_account ... where account_id = 'A'"); userTx.commit();} catch(SQLException sqle){ // 處理異常代碼 } catch(Exception ne){ e.printStackTrace(); } }</span> 五 總結
JTA的實現其實來源於JDBC的事務,JTA區別於JDBC的是:資源管理員需要實現XADatasource介面,只要實現了這個介面,就可以通過JTA的交易管理員實現交易管理。