JDBC事務和JTA事務
之前的事務介紹基本都是資料庫層面的事務,本文來介紹一下J2EE中和事務相關的內容,在閱讀本文之前,希望讀者對分散式有一定的瞭解。
關於事務的基礎知識這裡不再詳細介紹,想要了解的同學可以在我的部落格中閱讀相關文章。
Java事務的型別有三種:JDBC事務
、JTA(Java Transaction API)事務
、容器事務
。 常見的容器事務如Spring事務,容器事務主要是J2EE應用伺服器提供的,容器事務大多是基於JTA完成,這是一個基於JNDI的,相當複雜的API實現。所以本文暫不討論容器事務。本文主要介紹J2EE開發中兩個比較基本的事務:JDBC事務
和JTA事務
。
JDBC事務
JDBC的一切行為包括事務是基於一個Connection
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();
// 事務提交:轉賬的兩步操作同時成功