[轉]Spring 事務管理機制
摘要:
一般地,使用者的每次請求都對應一個業務邏輯方法,而一個業務邏輯方法往往包括一系列資料庫原子訪問操作,並且這些資料庫原子訪問操作應該繫結成一個事務來執行。然而,在使用傳統的事務程式設計策略時,程式程式碼必然和具體的事務操作程式碼耦合,而使用Spring事務管理策略恰好可以避免這種尷尬。Spring的事務管理提供了兩種方式:程式設計式事務管理和宣告式事務管理。本文通過在對Spring事務管理API分析的基礎上,詳細地闡述了Spring程式設計式事務管理和宣告式事務管理的原理、本質和使用。
一. Spring 事務概述
一般而言,使用者的每次請求都對應一個業務邏輯方法,並且每個業務邏輯方法往往具有邏輯上的原子性。此外,一個業務邏輯方法往往包括一系列資料庫原子訪問操作,並且這些資料庫原子訪問操作應該繫結成一個整體,即要麼全部執行,要麼全部不執行,通過這種方式我們可以保證資料庫的完整性,這就是事務。總的來說,事務是一個不可分割操作序列,也是資料庫併發控制的基本單位,其執行的結果必須使資料庫從一種一致性狀態變到另一種一致性狀態。
但是,在使用傳統的事務程式設計策略時,程式程式碼必然和具體的事務操作程式碼耦合,如下所示:
// JDBC事務
Connection conn = getConnection();
conn.setAutoCommit(false);
...
// 業務實現
...
if 正常
conn.commit();
if 失敗
conn.rollback();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
// Hibernate事務
Session s = getSession();
Transaction tx = s.beginTransaction();
...
// 業務實現
...
if 正常
tx.commit();
if 失敗
tx.rollback();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
因此,當應用需要在不同的事務策略之間切換時,開發者必須手動修改程式程式碼。使用Spring事務管理策略,就可以避免這種尷尬。因為Spring的事務管理不需與任何特定的事務API耦合,並且其提供了兩種事務管理方式:程式設計式事務管理和宣告式事務管理。對不同的持久層訪問技術,程式設計式事務提供一致的事務程式設計風格,通過模板化的操作一致性地管理事務;而宣告式事務基於Spring AOP實現,卻並不需要程式開發者成為AOP專家,亦可輕易使用Spring的宣告式事務管理。
二. Spring 事務管理 API
Spring 框架中,最重要的事務管理的 API 有三個:TransactionDefinition、PlatformTransactionManager 和 TransactionStatus。 所謂事務管理,實質上就是按照給定的事務規則來執行提交或者回滾操作
1、PlatformTransactionManager 介面
Spring事務策略是通過PlatformTransactionManager介面體現的,該介面是Spring事務策略的核心。該介面的原始碼如下:
public interfacePlatformTransactionManager {
//平臺無關的獲得事務的方法
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
//平臺無關的事務提交方法
void commit(TransactionStatus status) throws TransactionException;
//平臺無關的事務回滾方法
void rollback(TransactionStatus status) throws TransactionException;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
可以看出,PlatformTransactionManager是一個與任何事務策略分離的介面。PlatformTransactionManager介面有許多不同的實現類,應用程式面向與平臺無關的介面程式設計,而對不同平臺的底層支援由PlatformTransactionManager介面的實現類完成,故而應用程式無須與具體的事務API耦合。因此使用PlatformTransactionManager介面,可將程式碼從具體的事務API中解耦出來。
在PlatformTransactionManager介面內,包含一個getTransaction(TransactionDefinition definition)方法,該方法根據一個TransactionDefinition引數,返回一個TransactionStatus物件。TransactionStatus物件表示一個事務,該事務可能是一個新的事務,也可能是一個已經存在的事務物件,這由TransactionDefinition所定義的事務規則所決定。
2、TransactionDefinition 介面
TransactionDefinition 介面用於定義一個事務的規則,它包含了事務的一些靜態屬性,比如:事務傳播行為、超時時間等。同時,Spring 還為我們提供了一個預設的實現類:DefaultTransactionDefinition,該類適用於大多數情況。如果該類不能滿足需求,可以通過實現 TransactionDefinition 介面來實現自己的事務定義。
TransactionDefinition介面包含與事務屬性相關的方法,如下所示:
public interfaceTransactionDefinition{
int getIsolationLevel();
int getPropagationBehavior();
int getTimeout();
boolean isReadOnly();
}
- 1
- 2
- 3
- 4
- 5
- 6
TransactionDefinition 介面只提供了獲取屬性的方法,而沒有提供相關設定屬性的方法。因為,事務屬性的設定完全是程式設計師控制的,因此程式設計師可以自定義任何設定屬性的方法,而且儲存屬性的欄位也沒有任何要求。唯一的要求的是,Spring 進行事務操作的時候,通過呼叫以上介面提供的方法必須能夠返回事務相關的屬性取值。例如,TransactionDefinition 介面的預設的實現類 —— DefaultTransactionDefinition 就同時定義了一系列屬性設定和獲取方法。
TransactionDefinition 介面定義的事務規則包括:事務隔離級別、事務傳播行為、事務超時、事務的只讀屬性和事務的回滾規則,下面我們一一詳細介紹。
(1). 事務隔離級別
所謂事務的隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 介面中定義了五個表示隔離級別的常量:
TransactionDefinition.ISOLATION_DEFAULT:這是預設值,表示使用底層資料庫的預設隔離級別。對大部分資料庫而言,該級別就是 TransactionDefinition.ISOLATION_READ_COMMITTED;
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料,該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別;
TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的資料。該級別可以防止髒讀,這也是大多數情況下的推薦值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是,這將嚴重影響程式的效能,通常情況下也不會用到該級別。
Ps : 關於資料庫事務併發機制及髒讀、不可重複讀和幻讀等概念的介紹,請移步我的博文《簡述資料庫事務併發機制》。
(2). 事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。TransactionDefinition介面定義瞭如下幾個表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
- TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
這裡需要指出的是,以 PROPAGATION_NESTED 啟動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,巢狀的子事務不能單獨提交。另外,外部事務的回滾也會導致巢狀子事務的回滾。
(3). 事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
(4). 事務的只讀屬性
事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如資料來源、 JMS 資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌為只讀的,以提高事務處理的效能。在 TransactionDefinition介面中,以 boolean 型別來表示該事務是否只讀。
(5). 事務的回滾規則
通常情況下,如果在事務中丟擲了未檢查異常(繼承自 RuntimeException 的異常),則預設將回滾事務。如果沒有丟擲任何異常,或者丟擲了已檢查異常,則仍然提交事務。這通常也是大多數開發者希望的處理方式,也是 EJB 中的預設處理方式。但是,我們可以根據需要人為控制事務在丟擲某些未檢查異常時任然提交事務,或者在丟擲某些已檢查異常時回滾事務。
3、TransactionStatus 介面
PlatformTransactionManager.getTransaction(…) 方法返回一個 TransactionStatus 物件,該物件可能代表一個新的或已經存在的事務(如果在當前呼叫堆疊有一個符合條件的事務)。TransactionStatus 介面提供了一個簡單的控制事務執行和查詢事務狀態的方法。該介面的原始碼如下:
public interfaceTransactionStatus{
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
}
- 1
- 2
- 3
- 4
- 5
三. Spring 程式設計式事務管理
在 Spring 出現以前,程式設計式事務管理對基於 POJO 的應用來說是唯一選擇。用過 Hibernate 的人都知道,我們需要在程式碼中顯式呼叫beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。通過 Spring 提供的事務管理 API,我們可以在程式碼中靈活控制事務的執行。在底層,Spring 仍然將事務操作委託給底層的持久化框架來執行。
1、基於底層 API 的程式設計式事務管理 下面給出一個基於底層 API 的程式設計式事務管理的示例, 基於PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三個核心介面,我們完全可以通過程式設計的方式來進行事務管理。
public classBankServiceImplimplementsBankService {
private BankDao bankDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
......
public boolean transfer(Long fromId, Long toId, double amount) {
// 獲取一個事務
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try {
result = bankDao.transfer(fromId, toId, amount);
txManager.commit(txStatus); // 事務提交
} catch (Exception e) {
result = false;
txManager.rollback(txStatus); // 事務回滾
System.out.println("Transfer Error!");
}
return result;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
相應的配置檔案如下所示:
<beanid="bankService"class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
<propertyname="bankDao"ref="bankDao"/>
<propertyname="txManager"ref="transactionManager"/>
<propertyname="txDefinition">
<beanclass="org.springframework.transaction.support.DefaultTransactionDefinition">
<propertyname="propagationBehaviorName"value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如上所示,我們在BankServiceImpl類中增加了兩個屬性:一個是 TransactionDefinition 型別的屬性,它用於定義事務的規則;另一個是 PlatformTransactionManager 型別的屬性,用於執行事務管理操作。如果一個業務方法需要新增事務,我們首先需要在方法開始執行前呼叫PlatformTransactionManager.getTransaction(…) 方法啟動一個事務;建立並啟動了事務之後,便可以開始編寫業務邏輯程式碼,然後在適當的地方執行事務的提交或者回滾。
2、基於 TransactionTemplate 的程式設計式事務管理
當然,除了可以使用基於底層 API 的程式設計式事務外,還可以使用基於 TransactionTemplate 的程式設計式事務管理。通過上面的示例可以發現,上述事務管理的程式碼散落在業務邏輯程式碼中,破壞了原有程式碼的條理性,並且每一個業務方法都包含了類似的啟動事務、提交/回滾事務的樣板程式碼。Spring 也意識到了這些,並提供了簡化的方法,這就是 Spring 在資料訪問層非常常見的 模板回撥模式。
public classBankServiceImplimplementsBankService {
private BankDao bankDao;
private TransactionTemplate transactionTemplate;
......
public boolean transfer(final Long fromId, final Long toId, final double amount) {
return (Boolean) transactionTemplate.execute(new TransactionCallback(){
public Object doInTransaction(TransactionStatus status) {
Object result;
try {
result = bankDao.transfer(fromId, toId, amount);
} catch (Exception e) {
status.setRollbackOnly();
result = false;
System.out.println("Transfer Error!");
}
return result;
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
相應的配置檔案如下所示:
<beanid="bankService"class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">
<propertyname="bankDao"ref="bankDao"/>
<propertyname="transactionTemplate"ref="transactionTemplate"/>
</bean>
- 1
- 2
- 3
- 4
TransactionTemplate 的 execute() 方法有一個 TransactionCallback 型別的引數,該介面中定義了一個 doInTransaction() 方法,通常我們以匿名內部類的方式實現 TransactionCallback 介面,並在其 doInTransaction() 方法中書寫業務邏輯程式碼。這裡可以使用預設的事務提交和回滾規則,這樣在業務程式碼中就不需要顯式呼叫任何事務管理的 API。doInTransaction() 方法有一個TransactionStatus 型別的引數,我們可以在方法的任何位置呼叫該引數的 setRollbackOnly() 方法將事務標識為回滾的,以執行事務回滾。
此外,TransactionCallback 介面有一個子介面 TransactionCallbackWithoutResult,該介面中定義了一個 doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 介面主要用於事務過程中不需要返回值的情況。當然,對於不需要返回值的情況,我們仍然可以使用 TransactionCallback 介面,並在方法中返回任意值即可。
四. Spring 宣告式事務管理
Spring 的宣告式事務管理是建立在 Spring AOP 機制之上的,其本質是對目標方法前後進行攔截,並在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
宣告式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中作相關的事務規則宣告(或通過等價的基於標註的方式),便可以將事務規則應用到業務邏輯中。總的來說,宣告式事務得益於 Spring IoC容器 和 Spring AOP 機制的支援:IoC容器為宣告式事務管理提供了基礎設施,使得 Bean 對於 Spring 框架而言是可管理的;而由於事務管理本身就是一個典型的橫切邏輯(正是 AOP 的用武之地),因此 Spring AOP 機制是宣告式事務管理的直接實現者。
顯然,宣告式事務管理要優於程式設計式事務管理,這正是spring倡導的非侵入式的開發方式。宣告式事務管理使業務程式碼不受汙染,一個普通的POJO物件,只要在XML檔案中配置或者添加註解就可以獲得完全的事務支援。因此,通常情況下,筆者強烈建議在開發中使用宣告式事務,不僅因為其簡單,更主要是因為這樣使得純業務程式碼不被汙染,極大方便後期的程式碼維護。
1、基於 <tx> 名稱空間的宣告式事務管理 Spring 2.x 引入了 <tx> 名稱空間,結合使用 <aop> 名稱空間,帶給開發人員配置宣告式事務的全新體驗,配置變得更加簡單和靈活。總的來說,開發者只需基於<tx>和<aop>名稱空間在XML中進行簡答配置便可實現宣告式事務管理。下面基於<tx>使用Hibernate事務管理的配置檔案:
<!-- 配置 DataSourece -->
<beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<propertyname="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<propertyname="url">
<value>jdbc:mysql://localhost:3306/ssh</value>
</property>
<propertyname="username">
<value>root</value>
</property>
<propertyname="password">
<value>root</value>
</property>
</bean>
<!-- 配置 sessionFactory -->
<beanid="sessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<!-- 資料來源的設定 -->
<propertyname="dataSource"ref="dataSource" />
<!-- 用於持久化的實體類類列表 -->
<propertyname="annotatedClasses">
<list>
<value>cn.edu.tju.rico.model.entity.User</value>
<value>cn.edu.tju.rico.model.entity.Log</value>
</list>
</property>
<!-- Hibernate 的配置 -->
<propertyname="hibernateProperties">
<props>
<!-- 方言設定 -->
<propkey="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<!-- 顯示sql -->
<propkey="hibernate.show_sql">true</prop>
<!-- 格式化sql -->
<propkey="hibernate.format_sql">true</prop>
<!-- 自動建立/更新資料表 -->
<propkey="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置 TransactionManager -->
<beanid="txManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<propertyname="sessionFactory"ref="sessionFactory" />
</bean>
<!-- 配置事務增強處理的切入點,以保證其被恰當的織入 -->
<aop:config>
<!-- 切點 -->
<aop:pointcutexpression="execution(* cn.edu.tju.rico.service.impl.*.*(..))"id="bussinessService" />
<!-- 宣告式事務的切入 -->
<aop:advisoradvice-ref="txAdvice"pointcut-ref="bussinessService" />
</aop:config>
<!-- 由txAdvice切面定義事務增強處理 -->
<tx:adviceid="txAdvice"transaction-manager="txManager">
<tx:attributes>
<!-- get打頭的方法為只讀方法,因此將read-only設為 true -->
<tx:methodname="get*"read-only="true" />
<!-- 其他方法為讀寫方法,因此將read-only設為 false -->
<tx:methodname="*"read-only="false"propagation="REQUIRED"isolation="DEFAULT" />
</tx:attributes>
</tx:advice>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
事實上,Spring配置檔案中關於事務的配置總是由三個部分組成,即:DataSource、TransactionManager和代理機制三部分,無論哪種配置方式,一般變化的只是代理機制這部分。其中,DataSource、TransactionManager這兩部分只是會根據資料訪問方式有所變化,比如使用hibernate進行資料訪問時,DataSource實際為SessionFactory,TransactionManager的實現為 HibernateTransactionManager。如下圖所示:
2、基於 @Transactional 的宣告式事務管理
除了基於名稱空間的事務配置方式,Spring 還引入了基於 Annotation 的方式,具體主要涉及@Transactional 標註。@Transactional 可以作用於介面、介面方法、類以及類方法上:當作用於類上時,該類的所有 public 方法將都具有該型別的事務屬性;當作用於方法上時,該標註來覆蓋類級別的定義。如下所示:
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
return bankDao.transfer(fromId, toId, amount);
}
- 1
- 2
- 3
- 4
Spring 使用 BeanPostProcessor 來處理 Bean 中的標註,因此我們需要在配置檔案中作如下宣告來啟用該後處理 Bean,如下所示:
<tx:annotation-driventransaction-manager="transactionManager"/>
- 1
與前面相似,transaction-manager、datasource 和 sessionFactory的配置不變,只需將基於<tx>和<aop>名稱空間的配置更換為上述配置即可。
3、Spring 宣告式事務的本質
就Spring 宣告式事務而言,無論其基於 <tx> 名稱空間的實現還是基於 @Transactional 的實現,其本質都是 Spring AOP 機制的應用:即通過以@Transactional的方式或者XML配置檔案的方式向業務元件中的目標業務方法插入事務增強處理並生成相應的代理物件供應用程式(客戶端)使用從而達到無汙染地新增事務的目的。如下圖所示:
五. 更多
更多關於資料庫事務併發機制的介紹,請移步我的博文《簡述資料庫事務併發機制》。
更多關於 Java Web 方面的內容,請關注我的專欄 《Java Web 成神之路》。本專欄全面記錄了Java Web開發相關知識,不但包括對http, servlet,session等基礎知識的講解,還包括對流行框架(SSM,SpringMVC等)、中介軟體(Redis等)等進階知識的深入分析。筆者將持續跟進最新Web技術,期望對大家能夠起到拋磚引玉的效果。
引用