天天看點

java 事務

之前的事務介紹基本都是資料庫層面的事務,本文來介紹一下J2EE中和事務相關的内容,在閱讀本文之前,希望讀者對分布式有一定的了解。

關于事務的基礎知識這裡不再詳細介紹,想要了解的同學可以在我的部落格中閱讀相關文章。

Java事務的類型有三種:

JDBC事務

JTA(Java Transaction API)事務

容器事務

。 常見的容器事務如Spring事務,容器事務主要是J2EE應用伺服器提供的,容器事務大多是基于JTA完成,這是一個基于JNDI的,相當複雜的API實作。是以本文暫不讨論容器事務。本文主要介紹J2EE開發中兩個比較基本的事務:

JDBC事務

JTA事務

JDBC事務

JDBC的一切行為包括事務是基于一個

Connection

的,在JDBC中是通過

Connection

對象進行事務管理。在JDBC中,常用的和事務相關的方法是: 

setAutoCommit

commit

rollback

等。

下面看一個簡單的JDBC事務代碼:

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 - 500 where account_id = 'A'");
         // 将 B 賬戶中的金額增加 500 
         stmt.execute("\
         update t_account set amount = amount + 500 where 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事務

為什麼需要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();
             // 事務送出:轉賬的兩步操作同時成功