事務管理最佳實踐全面解析

pingyuan發表於2009-01-08
事務管理最佳實踐全面解析[@more@]
事務管理
企業級應用,或者叫“資訊管理系統”。這類軟體透過資料庫持久化儲存、處理的資訊。它們工作的核心,就是資料庫。這類應用,是目前市場上最主流的商業應用。
事務管理,這個概念出自於資料庫管理系統中。事務是一個單元的工作,要麼全做,要麼全不做。
事務管理對於維持資料庫系統內部儲存的資料邏輯上的一致性、完整性,起著至關重要的作用。如:一個銀行應用軟體中,轉帳的操作中,需要先在A使用者帳戶中減去資金,然後再在B使用者帳戶中增加相應的資金。如果完成A帳戶操作後,由於系統故障或者網路故障,沒有能夠完成接下來的操作,那麼A帳戶中的資金就白白流失了。顯然,客戶是無法接受這樣的結果的!
如果我們把一個A和B帳戶的操作放在一個事務單元中,那麼如果遇到上述異常情況,A帳戶減少資金的操作會回滾。A帳戶的資金不會減少。
事務管理和資料庫連線的關係
事務管理的工作,需要在資料庫連線上進行。如果沒有資料庫連線,事務管理是無法實施的。
因此,一個事務單元,應該小於或者等於一個資料庫連線的生命週期。
事務管理最佳模式
資料庫連線管理最佳模式
資料庫連線,是一種很寶貴也很昂貴的資源。一個資料庫可以提供的資料庫連線總數是有限的。而且,獲取一次資料庫連線也是非常昂貴的操作。需要建立網路連線。因此,我們應當儘可能的重用資料庫連線,讓資料庫連線維持的時間儘可能的長。
但是,我們也不能把資料庫連線維持的太久。因為,上文已經說過了,一個資料庫可以提供的資料庫連線總數是有限的。如果資料庫連線的時間很長,那麼其他需要資料庫連線的工作就無法得到所需的資料庫連線。
因此,最佳的資料庫連線模式,是“每次請求,一次資料庫連線”這樣的使用模式。
因為,多次請求之間的時間間隔是無法預料的,可能長達幾小時、甚至幾天。資料庫連線顯然不能白白的等待在那裡。而應該返回給資料庫,或者資料庫連線緩衝池,讓其他程式和元件有機會使用資料庫連線。
另外,如果一次資料庫連線,小於一次使用者請求,那麼,資料庫連線的得到和關閉次數又太頻繁了。因為,得到一次資料庫連線是非常消耗資源的。一次使用者請求,是一個短時、瞬間的操作,完全沒有必要使用多個資料庫連線。
另外,上文中說過,事務是依託在資料庫連線之上的。多個資料庫連線之間,是無法使用同一個事務的。(實際上,JTA分散式事務是可以在一個事務中使用多個資料庫連線的)
因此,我們更應該讓資料庫連線的生命週期儘可能的延長。
事務管理最佳模式
最佳的資料庫連線模式,是“每次請求,一次資料庫連線”這樣的使用模式。事務,與之相仿。最佳的事務管理模式,也是“每次請求,一次資料庫連線,一次事務”。
一次使用者請求,是使用者對軟體系統功能的一次獨立呼叫。使用者當然不希望他的一次操作,系統只執行一部分這種情況的發生。因此,對一次使用者請求的響應,使用一次事務,是非常和正確的。
對於一次單純的查詢操作,不更改持久化資料庫中記錄,那麼我們不需要使用事務。在資料庫操作發生錯誤時,丟擲異常,讓使用者介面顯示出問題即可。而對於更改資料庫記錄的操作,並且涉及到多次資料庫操作的,則必須使用事務,以保證資料庫中記錄的完整性和真實性。
資料庫連線和事務管理的反模式
資料庫連線和事務管理,在應用中有一些反模式。我們應該避免這樣做,否則會死得很慘!
一、資料庫連線和事務管理跨越一個客戶的多次請求
這樣的資料庫連線和事務,其持續時間是無法估量的。這樣嚴重影響軟體和資料庫的效能。這是絕對不可取的。
二、每個資料庫操作,一次資料庫連線和事務
這是一種非常常見的反模式。在採用DAO設計模式進行O-R對映中,DAO介面的一個資料庫訪問方法,就執行一次資料庫連線的獲取和釋放,並且執行一次或者多次事務。
如,下面的程式碼:
/*
4,刪除單條訊息
*/
publicvoid deleteMessage(String id){
Connection conn=DB.getConnection();
Statement stmt =null;
ResultSet rst=null;
try {
stmt = conn.createStatement();
//拼裝SQL
String sql="delete from message where id='"+id+"'";
stmt.executeUpdate(sql);
}
catch (SQLException ex) {
ex.printStackTrace();
thrownew DataAccessException();
}finally{
DB.freeDbResource(conn,stmt,rst);
}
}
這是典型的反模式。
資料庫連線在Dao中得到和釋放。如果一次使用者請求需要用到多個Dao方法,那麼就需要多次得到和釋放資料庫連線。造成了極大的浪費。而且,也無法對多個Dao方法實施事務管理。
另外,JDBC中,預設的事務管理方式是自動提交。上面的程式碼只有一個SQL執行語句。所有隻有一次事務。如果Dao方法中有多個SQL語句,那麼就會在一個Dao方法中使用多個事務,多次提交到資料庫中,這也是極端錯誤的!
當然,上面這個簡單的Dao方法,並不會造成任何實際的損害,這裡僅僅說明這種使用方式是一種反模式。
事務管理的最佳設計模式
最佳的事務管理模式,是“每次請求,一次資料庫連線,一次事務”。那麼,根據這個原則,具體我們應該怎樣編寫程式呢?
一、事務管理的分層
企業級應用軟體中的程式碼部分,可以分為以下幾個層次:
(一)控制器Controller層
這是表現層的業務委派。它處理使用者的請求,完成使用者要求的功能。它接收使用者傳來的引數,然後呼叫業務層的服務方法,完成所需的功能。
根據“每次請求,一次資料庫連線,一次事務”的原則。似乎,這裡是最好的得到和關閉資料庫連線,管理事務的地方。因為,Controller層中的每一個方法,對應著使用者的一次請求。
但是,我認為,這裡決不應該“得到和關閉資料庫連線,管理事務”。因為,首先,控制器層,作為表現層技術的一部分,它的作用,僅僅是委派操作給業務層的服務方法,應該儘可能的小。不應該包括這些程式碼。
其次,管理資料庫連線和事務,這是業務層的邏輯,應該在業務層處理,而不是在表現層處理。
更實際一點來說,Struts這種技術中,我們一般不使用Spring來管理Struts的控制器Action類。這樣,如果“得到和關閉資料庫連線,管理事務”放在Struts的控制器層—Action類中,那麼Spring自動管理資料庫連線和事務的宣告式事務管理機制就無法使用了!(當然,Struts的Action也可以配置成Spring管理。)
因此,我們應該堅決地拒絕在控制器層中處理資料庫連線和事務的誘惑!
(二)業務服務Service層
業務服務層,是業務邏輯的實際存放地。它們提供的服務分為2種:
1,為控制器層提供服務,處理使用者請求。
2,為其他類(不僅僅是控制器層,可能是其他Service方法等)提供服務。
傳統上,大家都不區分這兩類服務方法。統稱為Service。
而在我的方法中,我把它們區分開來。我把Service層的服務方法分為3類:
1,直接為控制器層提供服務,並且需要使用到資料庫操作,從而需要處理資料庫連線和事務的,我把它們成為Transaction方法。用*Transaction字尾標識。
這樣的方法,我仍然把它們放在Service介面中。當你需要實現這樣的方法時,看到字尾,你就知道,你需要在這裡呼叫Dao方法,並且“得到和關閉資料庫連線,管理事務”。
如果你不在這裡進行“得到和關閉資料庫連線,管理事務”的操作,那麼系統一定會出現資料庫訪問故障!
2,為其他類(可以是控制器層,也可能是其他Service方法等)提供服務,並且不需要訪問資料庫的方法。我稱它們為Service方法。使用*Service字尾,或者不使用字尾來標識它們。
這樣的方法,你可以無所顧忌的使用,既可以在控制器層中呼叫,也可以在任何程式碼中呼叫!
3,需要使用到資料庫操作,並且不可以直接被控制器層呼叫的方法。我稱它們為Dao方法。使用*Dao字尾來標識它們。
它們不是Dao介面中的方法,而是Service業務邏輯介面中的方法。我稱它們為Dao方法,並不是說,它們是Dao介面的方法,而是表示它們是Service層中需要使用Dao介面操縱資料庫的服務方法。並且,它們本身不含有“得到和關閉資料庫連線,管理事務”的程式碼。因此,所有需要呼叫它們的方法,需要注意了,“得到和關閉資料庫連線,管理事務”這些任務還沒有做。如果直接在控制器層呼叫它們,那麼一定會出現資料庫和事務的錯誤!
(三)DAO資料訪問層
DAO資料訪問模式,是目前在資料訪問層中用得最多的模式。在DAO中,使用各類資料庫訪問技術(如,JDBC,iBatis,Hibernate等)運算元據庫,實現O-R對映。
其中的方法,大都滿足“需要使用到資料庫操作,並且不可以直接被控制器層呼叫的方法”這樣一種情況。我們可以使用*Dao字尾來標識這些方法,也可以不使用字尾。因為Dao介面的方法,大抵都是這類方法。
二、資料庫連線和事務管理最佳模式
在我們的程式設計正規化中,是這樣工作的:
控制器層,接收使用者請求引數,並委派給業務層的Service介面執行業務邏輯。它可以直接呼叫Service介面的*Transaction方法和*Service方法或者沒有字尾的一般方法。
其中,*Transaction方法需要用到資料庫。其中必然呼叫了業務層的Dao方法,或者DAO層的資料庫訪問方法。其實現方法中必然有處理“得到和關閉資料庫連線,管理事務”的程式碼。
而*Service方法或者沒有字尾的一般方法,則沒有使用資料庫。
在DAO資料訪問層,執行資料庫操作的DAO方法,並不需要建立和關閉資料庫連線,也不需要處理事務。它們之需要得到資料庫連線,然後使用這個連線即可。(資料庫連線,可以透過引數從外部得到,也可以從本地執行緒變數中得到。後者是目前主流的技術)
這就是我提出的“事務管理最佳實踐”的工作情況。
在Service業務層和DAO資料訪問層中,我們都使用了“介面—實現類”相分離的設計模式。
一、程式設計方式的資料庫連線和事務管理
假設,現在我們使用多種資料庫訪問技術,來進行O-R對映。看看我們這個架構的適應能力。
我們的系統,分別使用JDBC,iBatis,Hibernate這三種資料庫訪問技術,使用程式設計方式手工管理資料庫連線和事務,不使用Spring這樣的IOC容器進行管理。看看我們需要做什麼:
(一)JDBC程式設計方式管理資料庫連線和事務
首先,開發一個JDBCUtil類,得到資料庫連線,並且把它們放在一個執行緒變數中,以便一個執行緒重用一個資料庫連線。
然後,開發DAO介面的實現類。實現DAO方法。從本地執行緒變數中得到資料庫連線,使用它。不需要關閉這個連線,也不需要管理事務。
接著,開發Serivce層的*Dao字尾命名的方法。它們只需要呼叫DAO介面的方法即可。不需要和資料庫連線、事務打交道。
最後,開發Service層的*Transaction字尾命名的方法。它們呼叫JDBCUtil類的方法,建立一個資料庫連線,並把它放在JDBCUtil類的本地執行緒變數中,設定conn.setAutoCommit(false);等待DAO介面的方法去取這個已經設為不自動提交的資料庫連線。
然後,在Try塊中,呼叫Dao方法(Service介面或者DAO介面的Dao方法)。呼叫結束之後,提交事務,並在異常處理模組中,設定回滾。最後,在finally塊中關閉資料庫連線,清除本地執行緒變數的值。
(二)iBatis程式設計方式管理資料庫連線和事務
iBatis本身就是使用本地執行緒變數來管理資料庫連線的。
1,DAO介面的實現方法中,呼叫iBatis程式碼,執行資料庫操作。
2,Service層的Dao方法,不需要任何更改。
3,Service層的Transaction方法,需要使用iBatis的事務管理程式碼。
private SqlMapClient sqlMap = XmlSqlMapBuilder.buildSqlMap(reader);
public updateItemDescriptionTransaction (String itemId, String newDescription) throws SQLException {
try {
sqlMap.startTransaction ();
dao方法呼叫;
sqlMap.commitTransaction ();
} finally {
sqlMap.endTransaction ();
}
}
iBatis處理事務的程式碼,也處理得資料庫連線。並且,事務的回滾也被iBatis搞定了。
也就是說,換了一種資料庫訪問技術,只需要改變Service層中*Transaction方法的實現和DAO層的實現。
(三)Hibernate程式設計方式管理資料庫連線和事務
Hibernate也是如此。
下面是Hibernate的助手類:
publicclass HibernateSessionFactoryFromJbpm {
privatestaticfinal ThreadLocal threadLocal = new ThreadLocal();
privatestatic org.hibernate.SessionFactory sessionFactory;
/**
*ReturnstheThreadLocalSessioninstance. Lazyinitialize
*theSessionFactoryifneeded.
*
* @returnSession
* @throwsHibernateException
*/
publicstatic Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get();
if (session == null || !session.isOpen()) {
if (sessionFactory == null) {
rebuildSessionFactory();
}
session = (sessionFactory != null) ? sessionFactory.openSession()
: null;
threadLocal.set(session);
}
return session;
}
/**
* Rebuildhibernatesessionfactory
*
*/
publicstaticvoid rebuildSessionFactory() {
try {
// configuration.configure(configFile);
//sessionFactory = configuration.buildSessionFactory();
sessionFactory =HibernateHelper.createSessionFactory();
} catch (Exception e) {
System.err
.println("%%%% Error Creating SessionFactory %%%%");
e.printStackTrace();
}
}
/**
* Closethesinglehibernatesessioninstance.
*
* @throwsHibernateException
*/
publicstaticvoid closeSession() throws HibernateException {
Session session = (Session) threadLocal.get();
threadLocal.set(null);
if (session != null) {
session.close();
}
}
}
Hibernate的Session,是對JDBC Connection的封裝。Hibernate不同於JDBC和iBatis。它預設就把自動提交設為false。也就是說,如果你不顯式的使用Hiberante事務,那麼根本不會運算元據庫!這點需要注意。
(四)Jbpm對Hiberante的封裝
另外,再說一下Jbpm對Hiberante所作的封裝。Jbpm使用的是Hiberante3的資料庫訪問技術。但是,它對Hibernate進行了封裝。
使用Jbpm,事務管理更加簡單。
如:
public List getAllCanSeenTaskInstancesTransaction (PageModule view,String userId) throws Exception {
JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
try {
returnthis.getAllCanSeenTaskInstances(view, userId);
}finally{
jbpmContext.close();
}
}
jbpmContext.close();方法執行時,自動提交事務。如果發生異常,自動回滾。並且,最後會關閉Hiberante本地執行緒中的Session,並清空該執行緒變數。
二、宣告方式的資料庫連線和事務管理
Spring容器管理業務程式碼和DAO資料訪問程式碼,是現在非常常用的一種方式。使用Spring時,我們一般使用Spring宣告式事務來管理資料庫連線和事務。
另外,還有EJB容器也有宣告式事務管理的機制,兩者的使用方法大體相同,我就不再論述,這裡只說Spring。
Spring管理下的JDBC,iBatis,Hibernate資料庫訪問方法。我們在DAO介面的實現類中,可以使用Spring提供的助手類的便利方法,進行資料庫操作。也可以使用Spring提供的助手類,得到Connection,Session等進行資料庫操作。或者使用Spring助手類的execute()方法呼叫資料庫操作程式碼。
如果你原先使用自己的助手類得到Connection,Session。那麼你完全可以修改該助手類的實現方法,改為從Spring得到Connection,Session。這樣就不需要修改DAO介面的實現類!
Service層中的Dao方法,仍然無需修改。
對於Service層中的Transaction方法。我們需要去除“得到和關閉資料庫連線,管理事務”的程式碼。然後,在Spring的配置檔案中,對其應用宣告式事務管理。執行時,Spring會透過SpringAOP技術,自動得到資料庫連線,管理事務。
可見,使用宣告式事務管理,我們只需要修改得到資料庫連線或者會話的Util助手類,以及Transaction方法即可。
綜上所述,可以看到,我提出的這一套事務管理最佳實踐是一套非常靈活、強大、簡潔的管理事務的最佳實踐。具有極其強大的適應能力。採用這套程式設計正規化,你可以很容易地徹底擺脫事務管理帶來的困擾!
使用它,即使是程式設計方式管理事務,也是非常簡單而可愛的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7199667/viewspace-1015890/,如需轉載,請註明出處,否則將追究法律責任。

相關文章