Java 處理事務(wù)精要
[摘要] 有一個(gè)訂單庫存管理系統(tǒng),每一次生成訂單的同時(shí)我們都要消減庫存。通常來說訂單和庫存在數(shù)據(jù)庫里是分兩張表來保存的:訂單表,庫存表。每一次追加一個(gè)訂單實(shí)際上需要兩步操作:在訂單表中插入一條數(shù)據(jù),同時(shí)修改庫存的數(shù)據(jù)。
[關(guān)鍵字] Java 事務(wù)
[關(guān)鍵字] Java 事務(wù)
簡單事務(wù)的概念
我不想從原理上說明什么是事務(wù),應(yīng)為那太枯燥了。我只想從一個(gè)簡單的例子來說明什么是事務(wù)。
例如我們有一個(gè)訂單庫存管理系統(tǒng),每一次生成訂單的同時(shí)我們都要消減庫存。通常來說訂單和庫存在數(shù)據(jù)庫里是分兩張表來保存的:訂單表,庫存表。每一次我們追加一個(gè)訂單實(shí)際上需要兩步操作:在訂單表中插入一條數(shù)據(jù),同時(shí)修改庫存的數(shù)據(jù)。
這樣問題來了,例如我們需要一個(gè)單位為10的訂單,庫存中有30件,理想的操作是我們?cè)谟唵伪碇胁迦肓艘粭l單位為10的訂單,之后將庫存表中的數(shù)據(jù)修改為20。但是有些時(shí)候事情并不是總是按照你的想法發(fā)生,例如:在你修改庫存的時(shí)候,數(shù)據(jù)庫突然由于莫名其妙的原因無法連接上了。也就是說庫存更新失敗了。但是訂單已經(jīng)產(chǎn)生了,那么怎么辦呢?沒辦法,只有手動(dòng)的修改。所以最好的方式是將訂單插入的操作和庫存修改的操作綁定在一起,必須同時(shí)成功或者什么都不做。這就是事務(wù)。
Java如何處理事務(wù)呢?
我們從java.sql.Connection說起,Connection表示了一個(gè)和數(shù)據(jù)庫的鏈接,可以通過Connection來對(duì)數(shù)據(jù)庫操作。在通常情況是Connection的屬性是自動(dòng)提交的,也就是說每次的操作真的更新了數(shù)據(jù)庫,真的無法回退了。針對(duì)上述的例子,一旦庫存更新失敗了,訂單無法回退,因?yàn)橛唵握娴牟迦氲搅藬?shù)據(jù)庫中。這并不是我們希望的。
我們希望的是:看起來成功了,但是沒有真的操作數(shù)據(jù)庫,知道我想讓他真的發(fā)生。可以通過Connection的setAutoCommit(false)讓Connection不自動(dòng)提交你的數(shù)據(jù),除非你真的想提交。那么如何讓操作真的發(fā)生呢?可以使用Connection的commit方法。如何讓操作回退呢?使用rollback方法。
例如:
try{
Connection conn = getConnection(); // 不管如何我們得到了鏈接
conn.setAutoCommit(false);
// 插入訂單
// 修改庫存
conn.commit(); // 成功的情況下,提交更新。
} catch(SQLException ex) {
conn.rollback(); // 失敗的情況下,回滾所有的操作
} finally {
conn.close();
}
這里有一點(diǎn)非常重要,事務(wù)是基于數(shù)據(jù)庫鏈接的。所以在但數(shù)據(jù)庫的情況下,事務(wù)操作很簡單。
那么如果表分布在兩個(gè)不同的數(shù)據(jù)庫中呢?
例如訂單表在訂單庫中,庫存表在庫存庫中,那么我們?nèi)绾翁幚磉@樣的事務(wù)呢?
需要注意,提交也可以遇到錯(cuò)誤呀!
try{
Connection conn1 = getConnection1();
Connection conn2 = getConnection2();
// 基于conn1做插入操作
// 基于conn2做更新操作
try{
conn1.commit()
} catch(SQLExcetion ) {
conn1.rollback();
}
try {
conn2.commit();
} catch(SQLException ) {
conn2.rollbakc();
// 保證肯定刪除剛才插入的訂單。
}
} catch(SQLException ex) {
// 如果插入失敗,conn1.rollback
// 如果更新失敗,conn1.rollback && conn2.rollback
} finally {
conn1.close();
conn2.close();
}
看看上述的代碼就知道,其實(shí)操作非常的復(fù)雜,甚至:保證肯定刪除剛才插入的訂單根本無法保證。
在上述情況下的事務(wù)可以稱之為分布式事務(wù),通過上述的代碼中事務(wù)同時(shí)提交處理的部分我們可以得出,要想處理分布式事務(wù),必須有獨(dú)立于數(shù)據(jù)庫的第三方的事務(wù)處理組件。
幸運(yùn)的是通常情況下,JavaEE兼容的應(yīng)用服務(wù)器,例如:Weblogic,Websphere,JBoss,Glassfish等都有這種分布式事務(wù)處理的組件。
如何使用應(yīng)用服務(wù)器的分布式事務(wù)管理器處理分布式事務(wù)?
以galssfish為例
1 建立對(duì)應(yīng)兩個(gè)數(shù)據(jù)庫的XA(javax.sql.XADataSource)類型的數(shù)據(jù)源。
2 使用UserTransaction來保證分布式事務(wù)。
try{
Connection conn1 = datasource1.getConnection();
Connection conn2 = datasource2.getConnection();
UserTransaction ut = getUserTransaction();
ut.begin();
// 插入訂單
// 修改庫存
ut.commit(); // 成功的情況下,提交更新。
} catch(SQLException ex) {
ut.rollback(); // 失敗的情況下,回滾所有的操作
} finally {
conn.close();
}
如何獲取UserTransaction呢?可以使用如下方法
UserTransaction tx = (UserTransaction)
ctx.lookup("jndi/UserTransaction");
我不想從原理上說明什么是事務(wù),應(yīng)為那太枯燥了。我只想從一個(gè)簡單的例子來說明什么是事務(wù)。
例如我們有一個(gè)訂單庫存管理系統(tǒng),每一次生成訂單的同時(shí)我們都要消減庫存。通常來說訂單和庫存在數(shù)據(jù)庫里是分兩張表來保存的:訂單表,庫存表。每一次我們追加一個(gè)訂單實(shí)際上需要兩步操作:在訂單表中插入一條數(shù)據(jù),同時(shí)修改庫存的數(shù)據(jù)。
這樣問題來了,例如我們需要一個(gè)單位為10的訂單,庫存中有30件,理想的操作是我們?cè)谟唵伪碇胁迦肓艘粭l單位為10的訂單,之后將庫存表中的數(shù)據(jù)修改為20。但是有些時(shí)候事情并不是總是按照你的想法發(fā)生,例如:在你修改庫存的時(shí)候,數(shù)據(jù)庫突然由于莫名其妙的原因無法連接上了。也就是說庫存更新失敗了。但是訂單已經(jīng)產(chǎn)生了,那么怎么辦呢?沒辦法,只有手動(dòng)的修改。所以最好的方式是將訂單插入的操作和庫存修改的操作綁定在一起,必須同時(shí)成功或者什么都不做。這就是事務(wù)。
Java如何處理事務(wù)呢?
我們從java.sql.Connection說起,Connection表示了一個(gè)和數(shù)據(jù)庫的鏈接,可以通過Connection來對(duì)數(shù)據(jù)庫操作。在通常情況是Connection的屬性是自動(dòng)提交的,也就是說每次的操作真的更新了數(shù)據(jù)庫,真的無法回退了。針對(duì)上述的例子,一旦庫存更新失敗了,訂單無法回退,因?yàn)橛唵握娴牟迦氲搅藬?shù)據(jù)庫中。這并不是我們希望的。
我們希望的是:看起來成功了,但是沒有真的操作數(shù)據(jù)庫,知道我想讓他真的發(fā)生。可以通過Connection的setAutoCommit(false)讓Connection不自動(dòng)提交你的數(shù)據(jù),除非你真的想提交。那么如何讓操作真的發(fā)生呢?可以使用Connection的commit方法。如何讓操作回退呢?使用rollback方法。
例如:
try{
Connection conn = getConnection(); // 不管如何我們得到了鏈接
conn.setAutoCommit(false);
// 插入訂單
// 修改庫存
conn.commit(); // 成功的情況下,提交更新。
} catch(SQLException ex) {
conn.rollback(); // 失敗的情況下,回滾所有的操作
} finally {
conn.close();
}
這里有一點(diǎn)非常重要,事務(wù)是基于數(shù)據(jù)庫鏈接的。所以在但數(shù)據(jù)庫的情況下,事務(wù)操作很簡單。
那么如果表分布在兩個(gè)不同的數(shù)據(jù)庫中呢?
例如訂單表在訂單庫中,庫存表在庫存庫中,那么我們?nèi)绾翁幚磉@樣的事務(wù)呢?
需要注意,提交也可以遇到錯(cuò)誤呀!
try{
Connection conn1 = getConnection1();
Connection conn2 = getConnection2();
// 基于conn1做插入操作
// 基于conn2做更新操作
try{
conn1.commit()
} catch(SQLExcetion ) {
conn1.rollback();
}
try {
conn2.commit();
} catch(SQLException ) {
conn2.rollbakc();
// 保證肯定刪除剛才插入的訂單。
}
} catch(SQLException ex) {
// 如果插入失敗,conn1.rollback
// 如果更新失敗,conn1.rollback && conn2.rollback
} finally {
conn1.close();
conn2.close();
}
看看上述的代碼就知道,其實(shí)操作非常的復(fù)雜,甚至:保證肯定刪除剛才插入的訂單根本無法保證。
在上述情況下的事務(wù)可以稱之為分布式事務(wù),通過上述的代碼中事務(wù)同時(shí)提交處理的部分我們可以得出,要想處理分布式事務(wù),必須有獨(dú)立于數(shù)據(jù)庫的第三方的事務(wù)處理組件。
幸運(yùn)的是通常情況下,JavaEE兼容的應(yīng)用服務(wù)器,例如:Weblogic,Websphere,JBoss,Glassfish等都有這種分布式事務(wù)處理的組件。
如何使用應(yīng)用服務(wù)器的分布式事務(wù)管理器處理分布式事務(wù)?
以galssfish為例
1 建立對(duì)應(yīng)兩個(gè)數(shù)據(jù)庫的XA(javax.sql.XADataSource)類型的數(shù)據(jù)源。
2 使用UserTransaction來保證分布式事務(wù)。
try{
Connection conn1 = datasource1.getConnection();
Connection conn2 = datasource2.getConnection();
UserTransaction ut = getUserTransaction();
ut.begin();
// 插入訂單
// 修改庫存
ut.commit(); // 成功的情況下,提交更新。
} catch(SQLException ex) {
ut.rollback(); // 失敗的情況下,回滾所有的操作
} finally {
conn.close();
}
如何獲取UserTransaction呢?可以使用如下方法
UserTransaction tx = (UserTransaction)
ctx.lookup("jndi/UserTransaction");
