本文轉自http://zhenchengchagangzi.iteye.com/blog/1159493html
java的事務處理,若是對數據庫進行屢次操做,每一次的執行或步驟都是一個事務.若是數據庫操做在某一步沒有執行或出現異常而致使事務失敗,這樣有的事務被執行有的就沒有被執行,從而就有了事務的回滾,取消先前的操做.....
注:在Java中使用事務處理,首先要求數據庫支持事務。如使用MySQL的事務功能,就要求MySQL的表類型爲Innodb才支持事務。不然,在Java程序中作了commit或rollback,但在數據庫中根本不能生效。
JavaBean中使用JDBC方式進行事務處理
public int delete(int sID) {
dbc = new DataBaseConnection();
Connection con = dbc.getConnection();
try {
con.setAutoCommit(false);// 更改JDBC事務的默認提交方式
dbc.executeUpdate("delete from xiao where ID=" + sID);
dbc.executeUpdate("delete from xiao_content where ID=" + sID);
dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);
con.commit();//提交JDBC事務
con.setAutoCommit(true);// 恢復JDBC事務的默認提交方式
dbc.close();
return 1;
}
catch (Exception exc) {
con.rollBack();//回滾JDBC事務
exc.printStackTrace();
dbc.close();
return -1;
}
}
在數據庫操做中,一項事務是指由一條或多條對數據庫更新的sql語句所組成的一個不可分割的工做單元。只有當事務中的全部操做都正常完成了,整個事務才能被提交到數據庫,若是有一項操做沒有完成,就必須撤消整個事務。
例如在銀行的轉賬事務中,假定張三從本身的賬號上把1000元轉到李四的賬號上,相關的sql語句以下:
update account set monery=monery-1000 where name='zhangsan'
update account set monery=monery+1000 where name='lisi'
這個兩條語句必須做爲一個完成的事務來處理。只有當兩條都成功執行了,才能提交這個事務。若是有一句失敗,整個事務必須撤消。
在connection類中提供了3個控制事務的方法:
(1) setAutoCommit(Boolean autoCommit):設置是否自動提交事務;
(2) commit();提交事務;
(3) rollback();撤消事務;
在jdbc api中,默認的狀況爲自動提交事務,也就是說,每一條對數據庫的更新的sql語句表明一項事務,操做成功後,系統自動調用commit()來提交,不然將調用rollback()來撤消事務。
在jdbc api中,能夠經過調用setAutoCommit(false) 來禁止自動提交事務。而後就能夠把多條更新數據庫的sql語句作爲一個事務,在全部操做完成以後,調用commit()來進行總體提交。假若其中一項 sql操做失敗,就不會執行commit()方法,而是產生相應的sqlexception,此時就能夠捕獲異常代碼塊中調用rollback()方法撤消事務。
事務處理是企業應用須要解決的最主要的問題之一。J2EE經過JTA提供了完整的事務管理能力,包括多個事務性資源的管理能力。可是大部分應用都是運行在單一的事務性資源之上(一個數據庫),他們並不須要全局性的事務服務。本地事務服務已然足夠(好比JDBC事務管理)。
本文並不討論應該採用何種事務處理方式,主要目的是討論如何更爲優雅地設計事務服務。僅以JDBC事務處理爲例。涉及到的DAO,Factory,Proxy,Decorator等模式概念,請閱讀相關資料。
也許你據說過,事務處理應該作在service層,也許你也正這樣作,可是否知道爲何這樣作?爲何不放在DAO層作事務處理。顯而易見的緣由是業務層 接口的每個方法有時候都是一個業務用例(User Case),它須要調用不一樣的DAO對象來完成一個業務方法。好比簡單地以網上書店購書最後的肯定定單爲例,業務方法首先是調用BookDAO對象(通常 是經過DAO工廠產生),BookDAO判斷是否還有庫存餘量,取得該書的價格信息等,而後調用 CustomerDAO從賬戶扣除相應的費用以及記錄信息,而後是其餘服務(通知管理員等)。簡化業務流程大概如此:
注意,咱們的例子忽略了鏈接的處理,只要保證同一個線程內取的是相同的鏈接便可(可用ThreadLocal實現):
首先是業務接口,針對接口,而不是針對類編程:
public interface BookStoreManager{
public boolean buyBook(String bookId,int quantity)throws SystemException;
....其餘業務方法
}
接下來就是業務接口的實現類??業務對象:
public class BookStoreManagerImpl implements BookStoreManager{
public boolean buyBook(String bookId)throws SystemException{
Connection conn=ConnectionManager.getConnection();//獲取數據庫鏈接
boolean b=false;
try{
conn.setAutoCommit(false); //取消自動提交
BookDAO bookDAO=DAOFactory.getBookDAO();
CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
//嘗試從庫存中取書
if(BookDAO.reduceInventory(conn,bookId,quantity)){
BigDecimal price=BookDAO.getPrice(bookId); //取價格
//從客戶賬戶中扣除price*quantity的費用
b=
CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
....
其餘業務方法,如通知管理員,生成定單等.
...
conn.commit(); //提交事務
conn.setAutoCommit(true);
}
}catch(SQLException e){
conn.rollback(); //出現異常,回滾事務
con.setAutoCommit(true);
e.printStackTrace();
throws new SystemException(e);
}
return b;
}
}
而後是業務表明工廠:
public final class ManagerFactory {
public static BookStoreManager getBookStoreManager() {
return new BookStoreManagerImpl();
}
}
這樣的設計很是適合於DAO中的簡單活動,咱們項目中的一個小系統也是採用這樣的設計方案,可是它不適合於更大規模的應用。首先,你有沒有聞到代碼重複的 bad smell?每次都要設置AutoCommit爲false,而後提交,出現異常回滾,包裝異常拋到上層,寫多了不煩纔怪,那能不能消除呢?其次,業務代 表對象如今知道它內部事務管理的全部的細節,這與咱們設計業務表明對象的初衷不符。對於業務表明對象來講,瞭解一個與事務有關的業務約束是至關恰當的,但 是讓它負責來實現它們就不太恰當了。再次,你是否想過嵌套業務對象的場景?業務表明對象之間的互相調用,層層嵌套,此時你又如何處理呢?你要知道按咱們現 在的方式,每一個業務方法都處於各自獨立的事務上下文當中(Transaction Context),互相調用造成了嵌套事務,此時你又該如何處理?也許辦法就是從新寫一遍,把不一樣的業務方法集中成一個巨無霸包裝在一個事務上下文中。
咱們有更爲優雅的設計來解決這類問題,若是咱們把Transaction Context的控制交給一個被業務表明對象、DAO和其餘Component所共知的外部對象。當業務表明對象的某個方法須要事務管理時,它提示此外部 對象它但願開始一個事務,外部對象獲取一個鏈接而且開始數據庫事務。也就是將事務控制從service層抽離,當 web層調用service層的某個業務表明對象時,返回的是一個通過Transaction Context外部對象包裝(或者說代理)的業務對象。此代理對象將請求發送給原始業務表明對象,可是對其中的業務方法進行事務控制。那麼,咱們如何實現 此效果呢?答案是JDK1.3引進的動態代理技術。動態代理技術只能代理接口,這也是爲何咱們須要業務接口BookStoreManager的緣由。
首先,咱們引入這個Transaction Context外部對象,它的代碼其實很簡單,若是不瞭解動態代理技術的請先閱讀其餘資料。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import com.strutslet.demo.service.SystemException;
public final class TransactionWrapper {
/**
* 裝飾原始的業務表明對象,返回一個與業務表明對象有相同接口的代理對象
*/
public static Object decorate(Object delegate) {
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(), new XAWrapperHandler(
delegate));
}
//動態代理技術
static final class XAWrapperHandler implements InvocationHandler {
private final Object delegate;
XAWrapperHandler(Object delegate) {
this.delegate = delegate;
}
//簡單起見,包裝業務表明對象全部的業務方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
Connection con = ConnectionManager.getConnection();
try {
//開始一個事務
con.setAutoCommit(false);
//調用原始業務對象的業務方法
result = method.invoke(delegate, args);
con.commit(); //提交事務
con.setAutoCommit(true);
} catch (Throwable t) {
//回滾
con.rollback();
con.setAutoCommit(true);
throw new SystemException(t);
}
return result;
}
}
}
正如咱們所見,此對象只不過把業務對象須要事務控制的業務方法中的事務控制部分抽取出來而已。請注意,業務表明對象內部調用自身的方法將不會開始新的事務,由於這些調用不會傳給代理對象。如此,咱們去除了表明重複的味道。此時,咱們的業務表明對象修改爲:
public class BookStoreManagerImpl implements BookStoreManager {
public boolean buyBook(String bookId)throws SystemException{
Connection conn=ConnectionManager.getConnection();// 獲取數據庫鏈接
boolean b=false;
try{
BookDAO bookDAO=DAOFactory.getBookDAO();
CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
// 嘗試從庫存中取書
if(BookDAO.reduceInventory(conn,bookId,quantity)){
BigDecimal price=BookDAO.getPrice(bookId); // 取價格
// 從客戶賬戶中扣除price*quantity的費用
b=
CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
....
其餘業務方法,如通知管理員,生成定單等.
...
}
}catch(SQLException e){
throws new SystemException(e);
}
return b;
}
....
}
能夠看到,此時的業務表明對象專一於實現業務邏輯,它再也不關心事務控制細節,把它們所有委託給了外部對象。業務表明工廠也修改一下,讓它返回兩種類型的業務表明對象:
public final class ManagerFactory {
//返回一個被包裝的對象,有事務控制能力
public static BookStoreManager getBookStoreManagerTrans() {
return (BookStoreManager) TransactionWrapper
.decorate(new BookStoreManagerImpl());
}
//原始版本
public static BookStoreManager getBookStoreManager() {
return new BookStoreManagerImpl();
}
......
}
咱們在業務表明工廠上提供了兩種不一樣的對象生成方法:一個用於建立被包裝的對象,它會爲每次方法調用建立一個新的事務;另一個用於建立未被包裝的版本,它用於加入到已有的事務(好比其餘業務表明對象的業務方法),解決了嵌套業務表明對象的問題。
咱們的設計還不夠優雅,好比咱們默認全部的業務表明對象的方法調用都將被包裝在一個Transaction Context。可事實是不少方法也許並不須要與數據庫打交道,若是咱們能配置哪些方法須要事務聲明,哪些不須要事務管理就更完美了。解決辦法也很簡單, 一個XML配置文件來配置這些,調用時判斷便可。說到這裏,瞭解spring的大概都會意識到這不正是聲明式事務控制嗎?正是如此,事務控制就是AOP的 一種服務,spring的聲明式事務管理是經過AOP實現的。AOP的實現方式包括:動態代理技術,字節碼生成技術(如CGLIB庫),java代碼生成 (早期EJB採用),修改類裝載器以及源代碼級別的代碼混合織入(aspectj)等。咱們這裏就是利用了動態代理技術,只能對接口代理;對類的動態代理 可使用cglib庫
簡單事務的概念
我不想從原理上說明什麼是事務,應爲那太枯燥了。我只想從一個簡單的例子來講明什麼是事務。
例如咱們有一個訂單庫存管理系統,每一次生成訂單的同時咱們都要消減庫存。一般來講訂單和庫存在數據庫裏是分兩張表來保存的:訂單表,庫存表。每一次咱們追加一個訂單實際上須要兩步操做:在訂單表中插入一條數據,同時修改庫存的數據。
這樣問題來了,例如咱們須要一個單位爲10的訂單,庫存中有30件,理想的操做是咱們在訂單表中插入了一條單位爲10的訂單,以後將庫存表中的數據修 改成20。可是有些時候事情並非老是按照你的想法發生,例如:在你修改庫存的時候,數據庫忽然因爲莫名其妙的緣由沒法鏈接上了。也就是說庫存更新失敗了。可是訂單已經產生了,那麼怎麼辦呢?沒辦法,只有手動的修改。因此最好的方式是將訂單插入的操做和庫存修改的操做綁定在一塊兒,必須同時成功或者什麼都 不作。這就是事務。
Java如何處理事務呢?
咱們從java.sql.Connection提及,Connection表示了一個和數據庫的連接,能夠經過Connection來對數據庫操做。 在一般狀況是Connection的屬性是自動提交的,也就是說每次的操做真的更新了數據庫,真的沒法回退了。針對上述的例子,一旦庫存更新失敗了,訂單沒法回退,由於訂單真的插入到了數據庫中。這並非咱們但願的。
咱們但願的是:看起來成功了,可是沒有真的操做數據庫,知道我想讓他真的發生。能夠經過Connection的setAutoCommit (false)讓Connection不自動提交你的數據,除非你真的想提交。那麼如何讓操做真的發生呢?可使用Connection的commit方 法。如何讓操做回退呢?使用rollback方法。
例如:
try{
Connection conn = getConnection(); // 無論如何咱們獲得了連接
conn.setAutoCommit(false);
// 插入訂單
// 修改庫存
conn.commit(); // 成功的狀況下,提交更新。
} catch(SQLException ex) {
conn.rollback(); // 失敗的狀況下,回滾全部的操做
} finally {
conn.close();
}
這裏有一點很是重要,事務是基於數據庫連接的。因此在但數據庫的狀況下,事務操做很簡單。
那麼若是表分佈在兩個不一樣的數據庫中呢?
例如訂單表在訂單庫中,庫存表在庫存庫中,那麼咱們如何處理這樣的事務呢?
須要注意,提交也能夠遇到錯誤呀!
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();
}
看看上述的代碼就知道,其實操做很是的複雜,甚至:保證確定刪除剛纔插入的訂單根本沒法保證。
在上述狀況下的事務能夠稱之爲分佈式事務,經過上述的代碼中事務同時提交處理的部分咱們能夠得出,要想處理分佈式事務,必須有獨立於數據庫的第三方的事務處理組件。
幸運的是一般狀況下,JavaEE兼容的應用服務器,例如:Weblogic,Websphere,JBoss,Glassfish等都有這種分佈式事務處理的組件。
如何使用應用服務器的分佈式事務管理器處理分佈式事務?
以galssfish爲例
1 創建對應兩個數據庫的XA(javax.sql.XADataSource)類型的數據源。
2 使用UserTransaction來保證分佈式事務。
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");
J2EE開發人員使用數據訪問對象(DAO)設計模式把底層的數據訪問邏輯和高層的商務邏輯分開。實現DAO模式可以更加專一於編寫數據訪問代碼。這篇文章中,Java開發人員Sean C. Sullivan從三個方面討論DAO編程的結構特徵:事務劃分,異常處理,日誌記錄。
在最近的18個月,我和一個優秀的軟件開發團隊一塊兒工做,開發定製基於WEB的供應鏈管理應用程序.咱們的應用程序訪問普遍的持久層數據,包括出貨狀態,供應鏈制度,庫存,貨物發運,項目管理數據,和用戶屬性等.咱們使用JDBC API鏈接咱們公司的各類數據庫平臺,而且在整個應用程序中應用了DAO設計模式.
經過在整個應用程序中應用數據訪問對象(DAO)設計模式使咱們可以把底層的數據訪問邏輯和上層的商務邏輯分開.咱們爲每一個數據源建立了提供CRUD(建立,讀取,更新,刪除)操做的DAO類.
在本文中,我將向你介紹DAO的實現策略以及建立更好的DAO類的技術.我會明確的介紹日誌記錄,異常處理,和事務劃分三項技術.你將學在你的DAO類中怎樣把這三種技術結合在一塊兒.這篇文章假設你熟悉JDBC API,SQL和關係性數據庫編程.
咱們先來回顧一下DAO設計模式和數據訪問對象.
DAO基礎
DAO模式是標準的J2EE設計模式之一.開發人員使用這個模式把底層的數據訪問操做和上層的商務邏輯分開.一個典型的DAO實現有下列幾個組件:
1. 一個DAO工廠類;
2. 一個DAO接口;
3. 一個實現DAO接口的具體類;
4. 數據傳遞對象(有些時候叫作值對象).
具體的DAO類包含了從特定的數據源訪問數據的邏輯。在下面的這段中你將學到設計和實現數據訪問對象的技術。
事務劃分:
關於DAO要記住的一件重要事情是它們是事務性對象。每一個被DAO執行的操做(象建立,更新、或刪除數據)都是和事務相關聯的。一樣的,事務劃分(transaction demarcation)的概念是特別重要的。
事務劃分是在事務界定定義中的方式。J2EE規範爲事務劃分描述了兩種模式:編程性事務(programmatic)和聲明性事務(declarative)。下表是對這兩種模式的拆分:
聲明性事務劃分
編程性事務劃分
程序員使用EJB的佈署描述符聲明事務屬性
程序員擔負編寫事務邏輯代碼的責任。
運行時環境(EJB容器)使用這些屬性來自動的管理事務。
應用程序經過一個API接口來控制事務。
我將把注意力集中的編程性事務劃分上。
象前面的介紹同樣,DAOs是一些事務對象。一個典型的DAO要執行象建立、更新、和刪除這的事務性操做。在設計一個DAO時,首先要問本身以下問題:
一、 事務將怎樣開始?
二、 事務將怎樣結束?
三、 那個對象將承擔起動一個事務的責任?
四、 那個對象將承擔結束一個事務的責任?
五、 DAO應該承擔起動和結束事務的責任?
六、 應用程序須要交叉訪問多個DAO嗎?
七、 一個事務包含一個DAO仍是多個DAO?
八、 一個DAO包含其它的DAO中的方法嗎?
回答這些問題將有助於你爲DAO對象選擇最好的事務劃分策略。對ADO中的事務劃分有兩個主要的策略。一種方法是使用DAO承擔事務劃分的責任;另外一種是延期性事務,它把事務劃分到調用DAO對象的方法中。若是你選擇前者,你將要在DAO類中嵌入事務代碼。若是你選擇後者,事務代碼將被寫在DAO類的 外部。咱們將使用簡單的代碼實例來更好的理解這兩種方法是怎樣工做的。
實例1展現了一個帶有兩種數據操做的DAO:建立(create)和更新(update):
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
實例2展現了一個簡單的事務,事務劃分代碼是在DAO類的外部。注意:在這個例子中的調用者把多個DOA操做組合到這個事務中。
tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction
這種事務事務劃分策略對在一個單一事務中訪問多個DAO的應用程序來講尤其重要。
你便可使用JDBC API也可使用Java 事務API(JTA)來實現事務的劃分。JDBC事務劃分比JTA事務劃分簡單,可是JTA提供了更好的靈活性。在下面的這段中,咱們會進一步的看事務劃分機制。
使用JDBC的事務劃分
JDBC事務是使用Connection對象來控制的。JDBC的鏈接接口(java.sql.Connection)提供了兩種事務模式:自動提交和手動提交。Java.sql.Connection爲控制事務提供了下列方法:
.public void setAutoCommit(Boolean)
.public Boolean getAutoCommit()
.public void commit()
.public void rollback()
實例3展現怎樣使用JDBC API來劃分事務:
import java.sql.*;
import javax.sql.*;
// ...
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// ...
pstmt = conn.prepareStatement(";UPDATE MOVIES ...";);
pstmt.setString(1, ";The Great Escape";);
pstmt.executeUpdate();
// ...
conn.commit();
// ...
使用JDBC事務劃分,你可以把多個SQL語句組合到一個單一事務中。JDBC事務的缺點之一就是事務範圍被限定在一個單一的數據庫鏈接中。一個 JDBC事務不可以跨越多個數據庫。接下來,咱們會看到怎樣使用JTA來作事務劃分的。由於JTA不象JDBC那樣被普遍的瞭解,因此我首先概要的介紹一 下JTA。
JTA概要介紹
Java事務API(JTA;Java Transaction API)和它的同胞Java事務服務(JTS;Java Transaction Service),爲J2EE平臺提供了分佈式事務服務。一個分佈式事務(distributed transaction)包括一個事務管理器(transaction manager)和一個或多個資源管理器(resource manager)。一個資源管理器(resource manager)是任意類型的持久化數據存儲。事務管理器(transaction manager)承擔着全部事務參與單元者的相互通信的責任。下車站顯示了事務管理器和資源管理的間的關係。
JTA事務比JDBC事務更強大。一個JTA事務能夠有多個參與者,而一個JDBC事務則被限定在一個單一的數據庫鏈接。下列任一個Java平臺的組件均可以參與到一個JTA事務中:
.JDBC鏈接
.JDO PersistenceManager 對象
.JMS 隊列
.JMS 主題
.企業JavaBeans(EJB)
.一個用J2EE Connector Architecture 規範編譯的資源分配器。
使用JTA的事務劃分
要用JTA來劃分一個事務,應用程序調用javax.transaction.UserTransaction接口中的方法。示例4顯示了一個典型的JNDI搜索的UseTransaction對象。
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup(";java:comp/UserTransaction";);
UserTransaction utx = (UserTransaction) txObj;
應用程序有了UserTransaction對象的引用以後,就能夠象示例5那樣來起動事務。
utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement(";UPDATE MOVIES ...";);
pstmt.setString(1, ";Spinal Tap";);
pstmt.executeUpdate();
// ...
utx.commit();
// ...
當應用程序調用commit()時,事務管理器使用兩段提交協議來結束事務。JTA事務控制的方法:
.javax.transaction.UserTransaction接口提供了下列事務控制方法:
.public void begin()
.public void commit()
.public void rollback()
.public void getStatus()
.public void setRollbackOnly()
.public void setTransactionTimeout(int)
應用程序調用begin()來起動事務,便可調用commit()也能夠調用rollback()來結束事務。
使用JTA和JDBC
開發人員常用JDBC來做爲DAO類中的底層數據操做。若是計劃使用JTA來劃分事務,你將須要一個實現了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驅動。實現了這些接口的驅動將有能力參與到JTA事務中。一個XADataSource對象是一個XAConnection對象的工廠。XAConnections是參與到JTA事務中的鏈接。
你須要使用應用程序服務器管理工具來創建XADataSource對象。對於特殊的指令請參考應用程序服務器文檔和JDBC驅動文檔。
J2EE應用程序使用JNDI來查找數據源。一旦應用程序有了一個數據源對象的引用,這會調用javax.sql.DataSource.getConnection()來得到數據庫的鏈接。
XA鏈接區別於非XA鏈接。要記住的是XA鏈接是一個JTA事務中的參與者。這就意味着XA鏈接不支持JDBC的自動提交特性。也就是說應用程序沒必要 在XA鏈接上調用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,應 用程序應該使用UserTransaction.begin()、UserTransaction.commit()和 UserTransaction.rollback().
選擇最好的方法
咱們已經討論了JDBC和JTA是怎樣劃分事務的。每一種方法都有它的優勢,回此你須要決定爲你的應用程序選擇一個最適應的方法。在咱們團隊許多最近的對於事務劃分的項目中使用JDBC API來建立DAO類。這DAO類總結以下:
.事務劃分代碼被嵌入到DAO類內部
.DAO類使用JDBC API來進行事務劃分
.調用者沒有劃分事務的方法
.事務範圍被限定在一個單一的JDBC鏈接
JDBC事務對複雜的企業應用程序不老是有效的。若是你的事務將跨越多個DAO對象或多個數據庫,那麼下面的實現策略可能會更恰當:
.用JTA對事務進行劃分
.事務劃分代碼被DAO分開
.調用者承擔劃分事務的責任
.DAO參與一個全局的事務中
JDBC方法因爲它的簡易性而具備吸引力,JTA方法提供了更多靈活性。你選擇什麼樣的實現將依賴於你的應用程序的特定需求。
日誌記錄和DAO
一個好的DAO實現類將使用日誌記錄來捕獲有關它在運行時的行爲細節。你能夠選擇記錄異常、配置信息、鏈接狀態、JDBC驅動程序的元數據或查詢參數。日誌對開發整個階段都是有益的。我常常檢查應用程序在開發期間、測試期間和產品中的日誌記錄。
在這段中,咱們將展示一段如何把Jakarta Commaons Logging結合中一個DAO中的例子。在咱們開始以前,讓咱們先回顧一些基礎知識。
選擇一個日誌例庫
許多開發人員使用的基本日誌形式是:System.out.println和System.err.println.Println語句。這種形式快捷方便,但它們不能提供一個完整的日誌系統的的能力。下表列出了Java平臺的日誌類庫:
日誌類庫
開源嗎?
URL
Java.util.logging
否
http://java.sun.com/j2ee
Jakarta Log4j
是
http://hajarta.apache.org/log4j/
Jakarta Commons Logging
是
http:/Jakarta.apache.org/commons/logging.html
Java.util.logging是J2SE1.4平臺上的標準的API。可是,大多數開發人員都認爲Jakarta Log4j提供了更大的功能性和靈活性。Log4j超越java.util.logging的優勢之一就是它支持J2SE1.3和J2SE1.4平臺。
Jakarta Commons Logging可以被用於和java.util.loggin或Jakarta Log4j一塊兒工做。Commons Logging是一個把你的應用程序獨立於日誌實現的提取層。使用Commons Logging你可以經過改變一個配置文件來與下面的日誌實現來交換數據。Commons Logging被用於JAKARTA Struts1.1和Jakarta HttpClient2.0中。
一個日誌示例
示例7顯示了在一個DOA類中怎樣使用Jakarta Commons Logging
import org.apache.commons.logging.*;
class DocumentDAOImpl implements DocumentDAO
{
static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
public void deleteDocument(String id)
{
// ...
log.debug(";deleting document: "; + id);
// ...
try
{
// ... data operations ...
}
catch (SomeException ex)
{
log.error(";Unable to delete document"; ex);
// ... handle the exception ...
}
}
}
日誌是評估應用程序的基本部分。若是你在一個DAO中遇到了失敗,日誌常常會爲理解發生的什麼錯誤提供最好的信息。把日誌結合到你的DAO中,確保獲得調試和解決問題的有效手段。
DAO中的異常處理
咱們已經看了事務劃分和日誌記錄,而且如今對於它們是怎樣應用於數據訪問對象的有一個深刻的理解。咱們第三部分也是最後要討論的是異常處理。下面的一些簡單的異常處理方針使用你的DAO更容易使用,更加健壯和更具備可維護性。
在實現DAO模式的時候,要考濾下面的問題:
.在DAO的public接口中的方法將拋出被檢查的異常嗎?
.若是是,將拋出什麼樣的檢查性異常?
.在DAO實現類中怎能樣處理異常。
在用DAO模式工做的過程當中,咱們的團隊爲異常處理開發了一組方針。下面的這些方針會很大程度的改善你的DAO:
.DAO方法應該拋出有意義的異常。
.DAO方法不該該拋出java.lang.Exception異常。由於java.lang.Exception太通常化,它不能包含有關潛在問題的全部信息。
.DAO方法不該該拋出java.sql.SQLException異常。SQLException是一個底層的JDBC異常,DAO應用努力封裝JDBC異常而不該該把JDBC異常留給應用程序的其它部分。
.在DAO接口中的方法應該只拋出調用者指望處理的檢查性異常。若是調用者不能用適當的方法來處理異常,考濾拋出不檢查性(運行時run-time)異常。
.若是你的數據訪問代碼捕獲了一個異常,不可要忽略它。忽略捕獲異常的DAO是很處理的。
.使用異常鏈把底層的異常傳遞給高層的某個處理器。
.考濾定義一個標準的DAO異常類。Spring框架提供了一個優秀的預約義的DAO異常類的集合。
看Resources,查看有異常和異常處理技術的更詳細信息。
實現示例:MovieDAO
MoveDAO是一個示範了在這篇文章中所討論的全部技術,包括事務劃分、日誌記錄和異常處理。你會在Resources段找到MovieDAO的源代碼。它被分下面的三個包:
.daoexamples.exception
.daoexamples.move
.daoexamples.moviedemo
這個DAO模式的實現由下面的類和接口組成:
.daoexamples.movie.MovieDAOFactory
.daoexamples.movie.MovieDAO
.daoexamples.movie.MovieDAOImpl
.daoexamples.movie.MovieDAOImplJTA
.daoexamples.movie.Movie
.daoexamples.movie.MovieImple
.daoexamples.movie.MovieNotFoundException
.daoexamples.movie.MovieUtil
MovieDAO接口定義了DAO的數據操做。這個接口有以下五個方法:
.public Movie findMovieById(String id)
.public java.util.Collection findMoviesByYear(String year)
.public void deleteMovie(String id)
.public Movie createMovie(String rating,String year,String title)
.public void updateMovie(String id,String rating,String year,String title)
daoexamples.movie包包含了兩個MovieDAO接口的實現。每一個實現使用了一個同的事務劃分方法,以下表所示:
MovieDAOImpl
MovieDAOImplJTA
實現了MovieDAO接口嗎?
Yes
Yes
經過JNDI得到DataSource嗎?
Yes
Yes
從一個DataSource得到java.sql.Connection對象嗎?
Yes
Yes
DAO界定內部的事務嗎?
Yes
No
使用JDBC事務嗎?
Yes
No
使用一個XA DataSource嗎?
No
Yes
分擔JTA事務嗎?
No
Yes
MovieDAO 示範應用程序
這個示範應用程序是一個叫作daoexamples.moviedemo.DemoServlet.DemoServlet的servlet類,它使用Movie DAO來查詢和更新一個表中的movie數據。
這個servlet示範了把JTA感知的MovieDAO和Java消息服務組合到一個單一的事務中,如示例8所示:
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();
batman = dao.createMovie(";R";
";2008";
";Batman Reloaded";);
publisher = new MessagePublisher();
publisher.publishTextMessage(";I’ll be back";);
dao.updateMovie(topgun.getId(),
";PG-13";
topgun.getReleaseYear(),
topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();
要運行這個範例應用程序,在你的應用程序服務器中配置一個XA 數據源和一個非XA數據源。而後佈署daoexamples.ear文件。這個應用程序將運行在任何與J2EE兼容的應用程序服務器。
事務處理
信息是任何企事業單位的重要資產,任何企業部門都包含着信息的流入、流出,任何企業部門都控制着某些信息。同時,信息必須在適當的時機傳播給須要的 人。並且,信息還須要安全約束,一般根據信息的類型和內容實施訪問控制。爲了保證數據的安全有效和正確可靠,數據庫管理系統(DBMS)必須提供統一的數 據保護功能。
事務是現代數據庫理論中的核心概念之一。若是一組處理步驟或者所有發生或者一步也不執行,咱們稱該組處理步驟爲一個事務。當全部的步驟像一個操做同樣被完整地執行,咱們稱該事務被提交。因爲其中的一部分或多步執行失敗,致使沒有步驟被提交,則事務必須回滾(回到最初的系統狀態)。事務必須服從 ISO/IEC所制定的ACID原則。ACID是原子性(atomicity)、一致性(consistency)、隔離性(isolation)和持久 性(durability)的縮寫。事務的原子性表示事務執行過程當中的任何失敗都將致使事務所作的任何修改失效。一致性表示當事務執行失敗時,全部被該事務影響的數據都應該恢復到事務執行前的狀態。隔離性表示在事務執行過程當中對數據的修改,在事務提交以前對其餘事務不可見。持久性表示已提交的數據在事務執 行失敗時,數據的狀態都應該正確。
在下面咱們列舉一個使用SQL Server數據庫進行事務處理的例子。主表是一個規章制度信息表(bylaw),主要字段有記錄編號、標題、做者、書寫日期等。兩個子表分別是附件表 (bylaw_affix)和文本信息表(bylaw_content)。表結構見圖1所示。bylaw表的記錄編號與bylaw_affix表的記錄編 號、bylaw_content表的記錄編號是對應的,每次對規章制度信息的操做也就是對這三個表的聯合操做。例如要刪除規章制度中的一條記錄,若是不使用事務,就可能會出現這樣的狀況:第一個表中成功刪除後,數據庫忽然出現意外情況,而第2、三個表中的操做沒有完成,這樣,刪除操做並無完成,甚至已經 破壞數據庫中的數據。要避免這種狀況,就應該使用事務,它的做用是:要麼三個表都操做成功,要麼都失敗。換句話說,就是保持數據的一致性。因此,爲了確保對數據操做的完整和一致,在程序設計時要充分考慮到事務處理方面的問題。
圖1 示例表結構
Java中的事務處理
通常狀況下,J2EE應用服務器支持JDBC事務、JTA(Java Transaction API)事務、容器管理事務。通常狀況下,最好不要在程序中同時使用上述三種事務類型,好比在JTA事務中嵌套JDBC事務。第二方面,事務要在儘量短 的時間內完成,不要在不一樣方法中實現事務的使用。下面咱們列舉兩種事務處理方式。
一、JavaBean中使用JDBC方式進行事務處理
在JDBC中怎樣將多個SQL語句組合成一個事務呢?在JDBC中,打開一個鏈接對象Connection時,缺省是auto-commit模式,每一個 SQL語句都被看成一個事務,即每次執行一個語句,都會自動的獲得事務確認。爲了能將多個SQL語句組合成一個事務,要將auto-commit模式屏蔽 掉。在auto-commit模式屏蔽掉以後,若是不調用commit()方法,SQL語句不會獲得事務確認。在最近一次commit()方法調用以後的 全部SQL會在方法commit()調用時獲得確認。
public int delete(int sID) {
dbc = new DataBaseConnection();
Connection con = dbc.getConnection();
try {
con.setAutoCommit(false);// 更改JDBC事務的默認提交方式
dbc.executeUpdate("delete from bylaw where ID=" + sID);
dbc.executeUpdate("delete from bylaw _content where ID=" + sID);
dbc.executeUpdate("delete from bylaw _affix where bylawid=" + sID);
con.commit();//提交JDBC事務
con.setAutoCommit(true);// 恢復JDBC事務的默認提交方式
dbc.close();
return 1;
}
catch (Exception exc) {
con.rollBack();//回滾JDBC事務
exc.printStackTrace();
dbc.close();
return -1;
}
}
二、SessionBean中的JTA事務
JTA 是事務服務的 J2EE 解決方案。本質上,它是描述事務接口(好比 UserTransaction 接口,開發人員直接使用該接口或者經過 J2EE 容器使用該接口來確保業務邏輯可以可靠地運行)的 J2EE 模型的一部分。JTA 具備的三個主要的接口分別是 UserTransaction 接口、TransactionManager 接口和 Transaction 接口。這些接口共享公共的事務操做,例如 commit() 和 rollback(), 可是也包含特殊的事務操做,例如 suspend(),resume() 和 enlist(),它們只出如今特定的接口上,以便在實現中容許必定程度的訪問控制。例如,UserTransaction 可以執行事務劃分和基本的事務操做,而 TransactionManager 可以執行上下文管理。
應用程序能夠調用UserTransaction.begin()方法開始一個事務,該事務與應用程序正在其中運行的當前線程相關聯。底層的事務管理器實際處理線程與事務之間的關聯。UserTransaction.commit()方法終止與當前線程關聯的事務。 UserTransaction.rollback()方法將放棄與當前線程關聯的當前事務。
public int delete(int sID) {
DataBaseConnection dbc = null;
dbc = new DataBaseConnection();
dbc.getConnection();
UserTransaction transaction = sessionContext.getUserTransaction();//得到JTA事務
try {
transaction.begin(); //開始JTA事務
dbc.executeUpdate("delete from bylaw where ID=" + sID);
dbc.executeUpdate("delete from bylaw _content where ID=" + sID);
dbc.executeUpdate("delete from bylaw _affix where bylawid=" + sID);
transaction.commit(); //提交JTA事務
dbc.close();
return 1;
}
catch (Exception exc) {
try {
transaction.rollback();//JTA事務回滾
}
catch (Exception ex) {
//JTA事務回滾出錯處理
ex.printStackTrace();
}
exc.printStackTrace();
dbc.close();
return -1;
}
}
Can't start a cloned connection while in manual transaction mode錯誤2008-03-13 20:30出現Can't start a cloned connection while in manual transaction mode錯誤,從網上找到緣由及解決辦法以下:
緣由通常是當你在一個SQL SERVER的JDBC鏈接上執行多個STATEMENTS的操做,或者是手動事務狀態(AutoCommit=false) 而且使用默認的模式. direct (SelectMethod=direct) 模式.
解決辦法
當你使用手動事務模式時,必須把SelectMethod 屬性的值設置爲 Cursor, 或者是確保在你的鏈接只有一個STATEMENT操做。
修改url
加入SelectMethod=cursor便可
如:jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=ys;SelectMethod=Cursor;User=ys;Password=ys");
package _class;
import java.sql.*;
import java.util.StringTokenizer;
public class connDB{
String sDBDriver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
String sConnStr = "jdbc:microsoft:sqlserver://127.0.0.1:1433;SelectMethod=cursor;DatabaseName=myDB;user=sa;password=river";
Connection cn = null;
Statement stmt;
boolean autoCommit;
private String DbType="MYSQL";
//private String DbType="Oracle";
private connDB(){
init();
}
private void init(){
try{
Class.forName(sDBDriver).newInstance();
cn = DriverManager.getConnection(sConnStr);
}catch(Exception e){
System.err.println("conndb():鏈接異常. " + e.getMessage());
}
}
public static connDB getNewInstance(){
return new connDB();
}
//數據綁定的資料好像不多,有空給你們一個例子。在這裏只能返回PreparedStatement。
public PreparedStatement getPreparedStmt(String sql) throws SQLException{
PreparedStatement preStmt=null;
try{
preStmt = cn.prepareStatement(sql);
}catch(SQLException ex){
ex.printStackTrace();
throw ex;
}
return preStmt;
}
public void beginTrans() throws SQLException{
try{
autoCommit=cn.getAutoCommit();
cn.setAutoCommit(false);
}catch(SQLException ex){
ex.printStackTrace();
System.out.print("beginTrans Errors");
throw ex;
}
}
public void commit()throws SQLException{
try{
cn.commit();
cn.setAutoCommit(autoCommit);
}catch(SQLException ex){
ex.printStackTrace();
System.out.print("Commit Errors");
throw ex;
}
}
public void rollback(){
try{
cn.rollback();
cn.setAutoCommit(autoCommit);
}catch(SQLException ex){
ex.printStackTrace();
System.out.print("Rollback Errors");
//throw ex;
}
}
public boolean getAutoCommit() throws SQLException{
boolean result=false;
try{
result=cn.getAutoCommit();
}catch(SQLException ex){
ex.printStackTrace();
System.out.println("getAutoCommit fail"+ex.getMessage());
throw ex;
}
return result;
}
//默認的狀況下一次executeQuery(String sql)是一次事務。
//可是能夠調用beginTrans(),而後屢次executeQuery(String sql),最後commit()實現多sql的事務處理(注意在這種狀況下若是發生違例,千萬不要忘了在catch(){調用rollBack()})。
//
public ResultSet executeQuery(String sql) throws SQLException{
ResultSet rs = null;
try{
stmt=cn.createStatement();
rs = stmt.executeQuery(sql);
}
catch(SQLException ex)
{
ex.printStackTrace();
System.out.println("conndb.executeQuery:"+ex.getMessage());
throw ex;
}
return rs;
}
public void executeUpdate(String sql) throws SQLException{
try{
stmt=cn.createStatement();
stmt.executeUpdate(sql);
}catch(SQLException ex){
ex.printStackTrace();
System.out.println("conndb.executeUpdate:"+ex.getMessage());
throw ex;
}
}
//Method doBatch 的參數sql,是由一些sql語句拼起來的,用;隔開。能夠將許多的sql放在一個事務中,一次執行。
public int[] doBatch(String sql) throws SQLException{
int[] rowResult=null;
String a;
try{
//boolean autoCommit=cn.getAutoCommit();
//cn.setAutoCommit(false);
stmt=cn.createStatement();
StringTokenizer st = new StringTokenizer(sql,";");
while (st.hasMoreTokens()){
a=st.nextToken();
stmt.addBatch(a);
}
rowResult=stmt.executeBatch();
}catch(SQLException ex){
ex.printStackTrace();
System.out.println("conndb.doBatch:"+ex.getMessage());
throw ex;
}
return rowResult;
}
public String getDbType(){
return DbType;
}
public void close() throws SQLException{
try{
stmt.close();
stmt=null;
cn.close();
cn=null;
}catch(SQLException ex){
ex.printStackTrace();
System.out.println("Closeing connection fail"+ex.getMessage());
throw ex;
}
}
public static void main(String[] args)throws Exception{
connDB con=connDB.getNewInstance();
System.out.println(con.getDbType());
String sql2="insert into test values('0510321315','李白',80);";
String s1="select *from test";
con.beginTrans();
ResultSet rs=con.executeQuery(s1);
con.executeUpdate(sql2);System.out.println("con.executeUpdate(sql2);");
/*try{
int up=s.executeUpdate(sql2);
if(up!=0)System.out.println("語句:"+sql2+"插入成功!");
else System.out.println("語句:"+sql2+"插入失敗!");
}catch(SQLException e){System.out.println(e);}*/
//ResultSet rs=s.executeQuery("select *from titles");
con.executeUpdate("delete from test where sno='0510321315'");System.out.println("con.executeUpdate(\"delete from test where sno='0510321315'\");");
con.commit();
while(rs.next()){
System.out.print(rs.getString(1)+"\t");
System.out.print(rs.getString(2)+"\t");
System.out.print(rs.getInt(3)+"\t");
System.out.println(" ");
}
con.close();
}
}java