摘抄至網絡,以爲說的不錯,記下來,之後能夠看看。 sql
事務管理 數據庫
企業級應用,或者叫「信息管理系統」。這類軟件經過數據庫持久化保存、處理的信息。它們工做的核心,就是數據庫。這類應用,是目前市場上最主流的商業應用。
事務管理,這個概念出自於數據庫管理系統中。事務是一個單元的工做,要麼全作,要麼全不作。
事務管理對於維持數據庫系統內部保存的數據邏輯上的一致性、完整性,起着相當重要的做用。如:一個銀行應用軟件中,轉賬的操做中,須要先在A用戶賬戶中減去資金,而後再在B用戶賬戶中增長相應的資金。若是完成A賬戶操做後,因爲系統故障或者網絡故障,沒有可以完成接下來的操做,那麼A賬戶中的資金就白白流失了。顯然,客戶是沒法接受這樣的結果的!
若是咱們把一個A和B賬戶的操做放在一個事務單元中,那麼若是遇到上述異常狀況,A賬戶減小資金的操做會回滾。A賬戶的資金不會減小。
事務管理和數據庫鏈接的關係
事務管理的工做,須要在數據庫鏈接上進行。若是沒有數據庫鏈接,事務管理是沒法實施的。
所以,一個事務單元,應該小於或者等於一個數據庫鏈接的生命週期。
事務管理最佳模式
數據庫鏈接管理最佳模式
數據庫鏈接,是一種很寶貴也很昂貴的資源。一個數據庫能夠提供的數據庫鏈接總數是有限的。並且,獲取一次數據庫鏈接也是很是昂貴的操做。須要創建網絡鏈接。所以,咱們應當儘量的重用數據庫鏈接,讓數據庫鏈接維持的時間儘量的長。
可是,咱們也不能把數據庫鏈接維持的過久。由於,上文已經說過了,一個數據庫能夠提供的數據庫鏈接總數是有限的。若是數據庫鏈接的時間很長,那麼其餘須要數據庫鏈接的工做就沒法獲得所需的數據庫鏈接。
所以,最佳的數據庫鏈接模式,是「每次請求,一次數據庫鏈接」這樣的使用模式。
由於,屢次請求之間的時間間隔是沒法預料的,可能長達幾小時、甚至幾天。數據庫鏈接顯然不能白白的等待在那裏。而應該返回給數據庫,或者數據庫鏈接緩衝池,讓其餘程序和組件有機會使用數據庫鏈接。
另外,若是一次數據庫鏈接,小於一次用戶請求,那麼,數據庫鏈接的獲得和關閉次數又太頻繁了。由於,獲得一次數據庫鏈接是很是消耗資源的。一次用戶請求,是一個短時、瞬間的操做,徹底沒有必要使用多個數據庫鏈接。
另外,上文中說過,事務是依託在數據庫鏈接之上的。多個數據庫鏈接之間,是沒法使用同一個事務的。(實際上,JTA分佈式事務是能夠在一個事務中使用多個數據庫鏈接的)
所以,咱們更應該讓數據庫鏈接的生命週期儘量的延長。
事務管理最佳模式
最佳的數據庫鏈接模式,是「每次請求,一次數據庫鏈接」這樣的使用模式。事務,與之相仿。最佳的事務管理模式,也是「每次請求,一次數據庫鏈接,一次事務」。
一次用戶請求,是用戶對軟件系統功能的一次獨立調用。用戶固然不但願他的一次操做,系統只執行一部分這種狀況的發生。所以,對一次用戶請求的響應,使用一次事務,是很是和正確的。
對於一次單純的查詢操做,不更改持久化數據庫中記錄,那麼咱們不須要使用事務。在數據庫操做發生錯誤時,拋出異常,讓用戶界面顯示出問題便可。而對於更改數據庫記錄的操做,而且涉及到屢次數據庫操做的,則必須使用事務,以保證數據庫中記錄的完整性和真實性。
數據庫鏈接和事務管理的反模式
數據庫鏈接和事務管理,在應用中有一些反模式。咱們應該避免這樣作,不然會死得很慘!
1、數據庫鏈接和事務管理跨越一個客戶的屢次請求
這樣的數據庫鏈接和事務,其持續時間是沒法估量的。這樣嚴重影響軟件和數據庫的性能。這是絕對不可取的。
2、每一個數據庫操做,一次數據庫鏈接和事務
這是一種很是常見的反模式。在採用DAO設計模式進行O-R映射中,DAO接口的一個數據庫訪問方法,就執行一次數據庫鏈接的獲取和釋放,而且執行一次或者屢次事務。
如,下面的代碼:
/*
4,
刪除單條消息
*/
public
void
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();
throw
new
DataAccessException();
}
finally
{
DB.freeDbResource(conn,stmt,rst);
}
}
這是典型的反模式。
數據庫鏈接在Dao中獲得和釋放。若是一次用戶請求須要用到多個Dao方法,那麼就須要屢次獲得和釋放數據庫鏈接。形成了極大的浪費。並且,也沒法對多個Dao方法實施事務管理。
另外,JDBC中,默認的事務管理方式是自動提交。上面的代碼只有一個SQL執行語句。全部只有一次事務。若是Dao方法中有多個SQL語句,那麼就會在一個Dao方法中使用多個事務,屢次提交到數據庫中,這也是極端錯誤的!
固然,上面這個簡單的Dao方法,並不會形成任何實際的損害,這裏僅僅說明這種使用方式是一種反模式。
事務管理的最佳設計模式
最佳的事務管理模式,是「每次請求,一次數據庫鏈接,一次事務」。那麼,根據這個原則,具體咱們應該怎樣編寫程序呢?
1、事務管理的分層
企業級應用軟件中的代碼部分,能夠分爲如下幾個層次:
(一)控制器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接口的方法,大抵都是這類方法。
2、數據庫鏈接和事務管理最佳模式
在咱們的編程範式中,是這樣工做的:
控制器層,接收用戶請求參數,並委派給業務層的Service接口執行業務邏輯。它能夠直接調用Service接口的*Transaction方法和*Service方法或者沒有後綴的通常方法。
其中,*Transaction方法須要用到數據庫。其中必然調用了業務層的Dao方法,或者DAO層的數據庫訪問方法。其實現方法中必然有處理「獲得和關閉數據庫鏈接,管理事務」的代碼。
而*Service方法或者沒有後綴的通常方法,則沒有使用數據庫。
在DAO數據訪問層,執行數據庫操做的DAO方法,並不須要建立和關閉數據庫鏈接,也不須要處理事務。它們之須要獲得數據庫鏈接,而後使用這個鏈接便可。(數據庫鏈接,能夠經過參數從外部獲得,也能夠從本地線程變量中獲得。後者是目前主流的技術)
這就是我提出的「事務管理最佳實踐」的工做狀況。
在Service業務層和DAO數據訪問層中,咱們都使用了「接口—實現類」相分離的設計模式。
1、編程方式的數據庫鏈接和事務管理
假設,如今咱們使用多種數據庫訪問技術,來進行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的助手類:
public
class
HibernateSessionFactoryFromJbpm {
private
static
final
ThreadLocal
threadLocal
=
new
ThreadLocal();
private
static
org.hibernate.SessionFactory
sessionFactory
;
/**
*
Returns
the
ThreadLocal
Session
instance.
Lazy
initialize
*
the
<code>
SessionFactory
</code>
if
needed.
*
*/
public
static
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;
}
/**
*
Rebuild
hibernate
session
factory
*
*/
public
static
void
rebuildSessionFactory() {
try
{
// configuration.configure(configFile);
//sessionFactory = configuration.buildSessionFactory();
sessionFactory
=HibernateHelper.createSessionFactory();
}
catch
(Exception e) {
System.
err
.println(
"%%%% Error Creating SessionFactory %%%%"
);
e.printStackTrace();
}
}
/**
*
Close
the
single
hibernate
session
instance.
*
*/
public
static
void
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
{
return
this
.getAllCanSeenTaskInstances(view, userId);
}
finally
{
jbpmContext.close();
}
}
當
jbpmContext.close();
方法執行時,自動提交事務。若是發生異常,自動回滾。而且,最後會關閉
Hiberante
本地線程中的
Session
,並清空該線程變量。
2、聲明方式的數據庫鏈接和事務管理
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方法便可。
綜上所述,能夠看到,我提出的這一套事務管理最佳實踐是一套很是靈活、強大、簡潔的管理事務的最佳實踐。具備極其強大的適應能力。採用這套編程範式,你能夠很容易地完全擺脫事務管理帶來的困擾!
使用它,即便是編程方式管理事務,也是很是簡單而可愛的。