1. 程式人生 > >java JDBC事務和JTA事務詳解

java JDBC事務和JTA事務詳解

什麼是事務?

        事務其實就是一套資料庫操作集合,說到事務就不得不說它的四大特性(A C I D):原子性,一致性,隔離性,永續性。事務的原子性表示事務要麼被全部執行,要麼被全部不執行。如果事務下的子事務全部提交成功,則所有資料庫操作被提交,否則,應進行事務回滾。一致性,表示由一種正確的狀態轉換到另外一種正確的狀態。即當事務執行失敗時,所有被該事務影響的資料都應該恢復到事務執行前的狀態。隔離性表示相互間必須不能被影響。永續性表示事務提交後將被永久儲存,即便出現其他故障,事務處理結果也應得到儲存。永續性通過資料庫備份和恢復來保證。

        事務是現資料庫理論中的核心概念之一。如果一組處理操作步驟或者全部發生或者一步也不執行,我們稱該組處理步驟為一個事務。當所有的步驟像一個操作一樣被完整的執行,我們就稱該事務被提交。如果由於其中一步或者多步執行失敗,導致沒有步驟被提交,則事務必須回滾到最初的系統狀態。

         java中事務型別一般有三種:JDBC事務、JTA事務、容器事務。常見的容器事務如Spring事務,容器事務主要是J2EE應用服務提供,容器事務大多是基於JTA完成,這是一個基於JNDI(Java Naming and Directory Interface:一組幫助做多個命名和目錄服務介面的API。)的,相當複雜的API實現。所以這裡不討論容器事務。主要介紹兩個比較基本的事務: JDBC事務和JTA事務詳解。

        JDBC事務:JDBC的一切行為包括事務是基於一個Connection的,在JDBC中是通過Connection物件進行事務管理。在JDBC中,常用的和事務相關的方法是:setAutoCommit、commit、rollback等。

public void JdbcTransfer() {
    java.sql.Connection conn = null;
    try {
        conn = conn = DriverManager.getConnection("jdbc:oracle:thin:@host:1521:SID", "username", "userpwd");
        // 將自動提交設定為 false, 
        //若設定為 true 則資料庫將會把每一次資料更新認定為一個事務並自動提交 
conn.setAutoCommit(false);
        stmt = conn.createStatement();
        // 將 A 賬戶中的金額減少 500  
stmt.execute("update t_account set amount = amount - 500where account_id = 'A'"); // 將 B 賬戶中的金額增加 500 stmt.execute("update t_account set amount = amount + 500where account_id = 'B'"); // 提交事務 conn.commit(); // 事務提交:轉賬的兩步操作同時成功 } catch (SQLException sqle) { try { // 發生異常,回滾在本事務中的操做 conn.rollback(); // 事務回滾:轉賬的兩步操作完全撤銷 stmt.close(); conn.close(); } catch (Exception ignore) { } sqle.printStackTrace(); } }

上面程式碼是演示一個簡單的轉賬功能。通過事務來控制轉賬操作。要麼提交要麼回滾。

JDBC事務優缺點:

    JDBC為使用java進行資料庫的事務操作提供了最基本的支援。通過JDBC事務,我們可以將多個SQL語句放到同一個事務中,保證其ACID特性。JDBC事務的主要優點就是API比較簡單,可以實現最基本的事務操作,效能也相對較好。但是,JDBC事務有一個侷限:一個JDBC事務不能跨越多個數據庫!!!所以,如果涉及到多資料庫的操作或者分散式場景,JDBC事務就無能為力了。

JTA事務:

        通常,JDBC事務就可以解決資料的一致性等問題,鑑於他用法相對簡單,所以很多人關於Java中的事務只知道有JDBC事務,或者有人知道框架中的事務(比如Hibernate、Spring)等。但是,由於JDBC無法實現分散式事務,而如今的分散式場景越來越多,所以,JTA事務就應運而生。

如果,你在工作中沒有遇到JDBC事務無法解決的場景,那麼只能說你做的專案還都太小。拿電商網站來說,我們一般把一個電商網站橫向拆分成商品模組、訂單模組、購物車模組、訊息模組、支付模組等。然後我們把不同的模組部署到不同的機器上,各個模組之間通過遠端服務呼叫(RPC)等方式進行通訊。以一個分散式的系統對外提供服務。

一個支付流程就要和多個模組進行互動,每個模組都部署在不同的機器中,並且每個模組操作的資料庫都不一致,這時候就無法使用JDBC來管理事務。我們看一段程式碼:

/** 支付訂單處理 **/
@Transactional(rollbackFor = Exception.class)
public void completeOrder() {
    orderDao.update(); // 訂單服務本地更新訂單狀態 
accountService.update(); // 呼叫資金賬戶服務給資金帳戶加款 
pointService.update(); // 呼叫積分服務給積分帳戶增加積分 
accountingService.insert(); // 呼叫會計服務向會計系統寫入會計原始憑證 
merchantNotifyService.notify(); // 呼叫商戶通知服務向商戶傳送支付結果通知 
}

上面的程式碼是一個簡單的支付流程的操作,其中呼叫了五個服務,這五個服務都通過RPC的方式呼叫,請問使用JDBC如何保證事務一致性?我在方法中增加了@Transactional註解,但是由於採用呼叫了分散式服務,該事務並不能達到ACID的效果。

JTA事務比JDBC事務更強大。一個JTA事務可以有多個參與者,而一個JDBC事務則被限定在一個單一的資料庫連線。下列任一個Java平臺的元件都可以參與到一個JTA事務中:JDBC連線、JDO PersistenceManager 物件、JMS 佇列、JMS 主題、企業JavaBeans(EJB)、一個用J2EE Connector Architecture 規範編譯的資源分配器。

JTA的定義

Java事務API(Java Transaction API,簡稱JTA ) 是一個Java企業版 的應用程式介面,在Java環境中,允許完成跨越多個XA資源的分散式事務。

JTA和它的同胞Java事務服務(JTS;Java TransactionService),為J2EE平臺提供了分散式事務服務。不過JTA只是提供了一個介面,並沒有提供具體的實現,而是由j2ee伺服器提供商 根據JTS規範提供的,常見的JTA實現有以下幾種:

1.J2EE容器所提供的JTA實現(JBoss)

2.獨立的JTA實現:如JOTM,Atomikos.這些實現可以應用在那些不使用J2EE應用伺服器的環境裡用以提供分佈事事務保證。如Tomcat,Jetty以及普通的java應用。

JTA裡面提供了 java.transaction.UserTransaction ,裡面定義了下面幾個方法

  •  begin:開啟一個事務
  • commit:提交當前事務
  • rollback:回滾當前事務
  • setRollbackOnly:把當前事務標記為回滾
  • setTransactionTimeout:設定事務的事件,超過這個事件,就丟擲異常,回滾事務

這裡,值得注意的是,不是使用了UserTransaction就能把普通的JDBC操作直接轉成JTA操作,JTA對DataSource、Connection和Resource 都是有要求的,只有符合XA規範,並且實現了XA規範的相關介面的類才能參與到JTA事務中來,關於XA規範,請看我的另外一篇文章中有相關介紹。這裡,提一句,目前主流的資料庫都支援XA規範。

要想使用用 JTA 事務,那麼就需要有一個實現 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 介面的 JDBC 驅動程式。一個實現了這些介面的驅動程式將可以參與 JTA 事務。一個 XADataSource 物件就是一個 XAConnection 物件的工廠。XAConnection 是參與 JTA 事務的 JDBC 連線。

要使用JTA事務,必須使用XADataSource來產生資料庫連線,產生的連線為一個XA連線。

XA連線(javax.sql.XAConnection)和非XA(java.sql.Connection)連線的區別在於:XA可以參與JTA的事務,而且不支援自動提交。

public void JtaTransfer() {
    javax.transaction.UserTransaction tx = null;
    java.sql.Connection conn = null;
    try{
        tx = (javax.transaction.UserTransaction) context.lookup("java:comp/UserTransaction"); //取得JTA事務,本例中是由Jboss容器管理 
javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup("java:/XAOracleDS"); //取得資料庫連線池,必須有支援XA的資料庫、驅動程式  
tx.begin();
        conn = ds.getConnection();

        // 將自動提交設定為 false, 
        //若設定為 true 則資料庫將會把每一次資料更新認定為一個事務並自動提交 
conn.setAutoCommit(false);
        stmt = conn.createStatement();
        // 將 A 賬戶中的金額減少 500  
stmt.execute(" update t_account set amount = amount - 500 where account_id = 'A'"); 
                // 將 B 賬戶中的金額增加 500  
stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'");
                        // 提交事務 
tx.commit();
        // 事務提交:轉賬的兩步操作同時成功 
} catch(SQLException sqle){
        try{
            // 發生異常,回滾在本事務中的操做 
tx.rollback();
            // 事務回滾:轉賬的兩步操作完全撤銷 
stmt.close();
            conn.close();
        }catch(Exception ignore){
        }
        sqle.printStackTrace();
    }
}

上面的例子就是一個使用JTA事務的轉賬操作,該操作相對依賴於J2EE容器,並且需要通過JNDI的方式獲取UserTransaction和Connection。

標準的分散式事務

一個分散式事務(Distributed Transaction)包括一個事務管理器(transaction manager)和一個或多個資源管理器(resource manager)。一個資源管理器(resource manager)是任意型別的持久化資料儲存。事務管理器(transaction manager)承擔著所有事務參與單元者的相互通訊的責任。

看上面關於分散式事務的介紹是不是和2PC中的事務管理比較像?的卻,2PC其實就是符合XA規範的事務管理器協調多個資源管理器的一種實現方式。 我之前有幾篇文章關於2PC和3PC的,那幾篇文章中介紹過分散式事務中的事務管理器是如何協調多個事務的統一提交或回滾的,後面我還會有幾篇文章詳細的介紹一下和分散式事務相關的內容,包括但不限於全域性事務、DTP模型、柔性事務等。

JTA的優缺點

JTA的優點很明顯,就是提供了分散式事務的解決方案,嚴格的ACID。但是,標準的JTA方式的事務管理在日常開發中並不常用,因為他有很多缺點:

實現複雜

通常情況下,JTA UserTransaction需要從JNDI獲取。這意味著,如果我們使用JTA,就需要同時使用JTA和JNDI。
JTA本身就是個笨重的API。
通常JTA只能在應用伺服器環境下使用,因此使用JTA會限制程式碼的複用性。

總結

Java事務的型別有三種:JDBC事務、JTA(Java Transaction API)事務、容器事務,其中JDBC的事務操作用法比較簡單,適合於處理同一個資料來源的操作。JTA事務相對複雜,可以用於處理跨多個數據庫的事務,是分散式事務的一種解決方案。

這裡還要簡單說一下,雖然JTA事務是Java提供的可用於分散式事務的一套API,但是不同的J2EE平臺的實現都不一樣,並且都不是很方便使用,所以,一般在專案中不太使用這種較為負責的API。現在業內比較常用的分散式事務解決方案主要有非同步訊息確保型、TCC、最大努力通知等。