Spring 事務管理機制概述
寫在前面
一般地,使用者的每次請求都對應一個業務邏輯方法,而一個業務邏輯方法往往包括一系列資料庫原子訪問操作,並且這些資料庫原子訪問操作應該繫結成一個事務來執行。然而,在使用傳統的事務程式設計策略時,程式程式碼必然和具體的事務操作程式碼耦合,而使用Spring事務管理策略恰好可以避免這種尷尬。Spring的事務管理提供了兩種方式:程式設計式事務管理和宣告式事務管理
Spring 事務概述
使用者的每次請求都對應一個業務邏輯方法,並且每個業務邏輯方法往往具有邏輯上的原子性。此外,一個業務邏輯方法往往包括一系列資料庫原子訪問操作,並且這些資料庫原子訪問操作應該繫結成一個整體,即要麼全部執行,要麼全部不執行,通過這種方式我們可以保證資料庫的完整性,這就是事務。總的來說,事務是一個不可分割操作序列,也是資料庫併發控制的基本單位,其執行的結果必須使資料庫從一種一致性狀態變到另一種一致性狀態
Spring 事務管理 API
Spring 框架中,最重要的事務管理的 API 有三個:TransactionDefinition、PlatformTransactionManager 和 TransactionStatus
PlatformTransactionManager 介面
Spring事務策略是通過PlatformTransactionManager介面體現的,該介面是Spring事務策略的核心。該介面的原始碼如下:
public interface PlatformTransactionManager { //平臺無關的獲得事務的方法 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; //平臺無關的事務提交方法 void commit(TransactionStatus status) throws TransactionException; //平臺無關的事務回滾方法 void rollback(TransactionStatus status) throws TransactionException; }
可以看出,PlatformTransactionManager是一個與任何事務策略分離的介面 。PlatformTransactionManager介面有許多不同的實現類,應用程式面向與平臺無關的介面程式設計,而對不同平臺的底層支援由PlatformTransactionManager介面的實現類完成,故而應用程式無須與具體的事務API耦合。因此使用PlatformTransactionManager介面,可將程式碼從具體的事務API中解耦出來。
在PlatformTransactionManager介面內,包含一個getTransaction(TransactionDefinition definition)方法 ,該方法根據一個TransactionDefinition引數,返回一個TransactionStatus物件。TransactionStatus物件表示一個事務,該事務可能是一個新的事務,也可能是一個已經存在的事務物件,這由TransactionDefinition所定義的事務規則所決定。
TransactionDefinition 介面
TransactionDefinition 介面用於定義一個事務的規則,它包含了事務的一些靜態屬性,比如:事務傳播行為、超時時間等。同時,Spring 還為我們提供了一個預設的實現類:DefaultTransactionDefinition,該類適用於大多數情況。如果該類不能滿足需求,可以通過實現 TransactionDefinition 介面來實現自己的事務定義。
TransactionDefinition介面包含與事務屬性相關的方法,如下所示:
public interface TransactionDefinition{ int getIsolationLevel(); int getPropagationBehavior(); int getTimeout(); boolean isReadOnly(); }
TransactionDefinition 介面只提供了獲取屬性的方法,而沒有提供相關設定屬性的方法。因為,事務屬性的設定完全是程式設計師控制的,因此程式設計師可以自定義任何設定屬性的方法,而且儲存屬性的欄位也沒有任何要求。唯一的要求的是,Spring 進行事務操作的時候,通過呼叫以上介面提供的方法必須能夠返回事務相關的屬性取值。例如,TransactionDefinition 介面的預設的實現類 —— DefaultTransactionDefinition 就同時定義了一系列屬性設定和獲取方法。
TransactionDefinition 介面定義的事務規則包括:事務隔離級別、事務傳播行為、事務超時、事務的只讀屬性和事務的回滾規則,下面我們一一詳細介紹
事務隔離級別
所謂事務的隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 介面中定義了五個表示隔離級別的常量:
-
TransactionDefinition.ISOLATION_DEFAULT:這是預設值,表示使用底層資料庫的預設隔離級別。對大部分資料庫而言,該級別就是 TransactionDefinition.ISOLATION_READ_COMMITTED;
-
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料,該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別;
-
TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的資料。該級別可以防止髒讀,這也是大多數情況下的推薦值。
-
TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。
-
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是,這將嚴重影響程式的效能,通常情況下也不會用到該級別。
事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。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 啟動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,巢狀的子事務不能單獨提交。另外,外部事務的回滾也會導致巢狀子事務的回滾。
事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
事務的只讀屬性
事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如資料來源、 JMS 資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌為只讀的,以提高事務處理的效能。在 TransactionDefinition介面中,以 boolean 型別來表示該事務是否只讀。
事務的回滾規則
通常情況下,如果在事務中丟擲了未檢查異常(繼承自 RuntimeException 的異常),則預設將回滾事務。如果沒有丟擲任何異常,或者丟擲了已檢查異常,則仍然提交事務。這通常也是大多數開發者希望的處理方式,也是 EJB 中的預設處理方式。但是,我們可以根據需要人為控制事務在丟擲某些未檢查異常時任然提交事務,或者在丟擲某些已檢查異常時回滾事務。
TransactionStatus 介面
PlatformTransactionManager.getTransaction(…) 方法返回一個 TransactionStatus 物件,該物件可能代表一個新的或已經存在的事務(如果在當前呼叫堆疊有一個符合條件的事務)。TransactionStatus 介面提供了一個簡單的控制事務執行和查詢事務狀態的方法。該介面的原始碼如下:
publicinterface TransactionStatus{ boolean isNewTransaction(); void setRollbackOnly(); boolean isRollbackOnly(); }
Spring 程式設計式事務管理
在 Spring 出現以前,程式設計式事務管理對基於 POJO 的應用來說是唯一選擇 。用過 Hibernate 的人都知道,我們需要在程式碼中顯式呼叫beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。通過 Spring 提供的事務管理 API,我們可以在程式碼中靈活控制事務的執行 。在底層,Spring 仍然將事務操作委託給底層的持久化框架來執行。
基於底層 API 的程式設計式事務管理
下面給出一個基於底層 API 的程式設計式事務管理的示例,
基於PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三個核心介面,我們完全可以通過程式設計的方式來進行事務管理。
public class BankServiceImpl implements BankService { 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; } }
相應的配置檔案如下所示:
<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl"> <property name="bankDao" ref="bankDao"/> <property name="txManager" ref="transactionManager"/> <property name="txDefinition"> <bean class="org.springframework.transaction.support.DefaultTransactionDefinition"> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/> </bean> </property> </bean>
如上所示,我們在BankServiceImpl類中增加了兩個屬性:一個是 TransactionDefinition 型別的屬性,它用於定義事務的規則;另一個是 PlatformTransactionManager 型別的屬性,用於執行事務管理操作。如果一個業務方法需要新增事務,我們首先需要在方法開始執行前呼叫PlatformTransactionManager.getTransaction(…) 方法啟動一個事務;建立並啟動了事務之後,便可以開始編寫業務邏輯程式碼,然後在適當的地方執行事務的提交或者回滾。
基於 TransactionTemplate 的程式設計式事務管理
當然,除了可以使用基於底層 API 的程式設計式事務外,還可以使用基於 TransactionTemplate 的程式設計式事務管理。通過上面的示例可以發現,上述事務管理的程式碼散落在業務邏輯程式碼中,破壞了原有程式碼的條理性,並且每一個業務方法都包含了類似的啟動事務、提交/回滾事務的樣板程式碼。Spring 也意識到了這些,並提供了簡化的方法,這就是 Spring 在資料訪問層非常常見的 模板回撥模式。
public class BankServiceImpl implements BankService { 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; } } } }
相應的配置檔案如下所示:
<bean id="bankService" class="footmark.spring.core.tx.programmatic.template.BankServiceImpl"> <property name="bankDao" ref="bankDao"/> <property name="transactionTemplate" ref="transactionTemplate"/> </bean>
Spring 宣告式事務管理
Spring 的宣告式事務管理是建立在 Spring AOP 機制之上的,其本質是對目標方法前後進行攔截,並在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
宣告式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中作相關的事務規則宣告(或通過等價的基於標註的方式),便可以將事務規則應用到業務邏輯中。總的來說,宣告式事務得益於 Spring IoC容器 和 Spring AOP 機制的支援:IoC容器為宣告式事務管理提供了基礎設施,使得 Bean 對於 Spring 框架而言是可管理的;而由於事務管理本身就是一個典型的橫切邏輯(正是 AOP 的用武之地),因此 Spring AOP 機制是宣告式事務管理的直接實現者 。
基於 <tx> 名稱空間的宣告式事務管理
Spring 2.x 引入了 <tx> 名稱空間,結合使用 <aop> 名稱空間,帶給開發人員配置宣告式事務的全新體驗,配置變得更加簡單和靈活。總的來說,開發者只需基於<tx>和<aop>名稱空間在XML中進行簡答配置便可實現宣告式事務管理。下面基於<tx>使用Hibernate事務管理的配置檔案:
<!-- 配置 DataSourece --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- results in a setDriverClassName(String) call --> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/ssh</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value>root</value> </property> </bean>
<!-- 配置 sessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <!-- 資料來源的設定 --> <property name="dataSource" ref="dataSource" /> <!-- 用於持久化的實體類類列表 --> <property name="annotatedClasses"> <list> <value>cn.edu.tju.rico.model.entity.User</value> <value>cn.edu.tju.rico.model.entity.Log</value> </list> </property> <!-- Hibernate 的配置 --> <property name="hibernateProperties"> <props> <!-- 方言設定--> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <!-- 顯示sql --> <prop key="hibernate.show_sql">true</prop> <!-- 格式化sql --> <prop key="hibernate.format_sql">true</prop> <!-- 自動建立/更新資料表 --> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean>
<!-- 配置 TransactionManager --> <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 配置事務增強處理的切入點,以保證其被恰當的織入 --> <aop:config> <!-- 切點 --> <aop:pointcut expression="execution(* cn.edu.tju.rico.service.impl.*.*(..))" id="bussinessService" /> <!-- 宣告式事務的切入 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="bussinessService" /> </aop:config> <!-- 由txAdvice切面定義事務增強處理 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- get打頭的方法為只讀方法,因此將read-only設為 true --> <tx:method name="get*" read-only="true" /> <!-- 其他方法為讀寫方法,因此將read-only設為 false --> <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" /> </tx:attributes> </tx:advice>
基於 @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); }
Spring 使用 BeanPostProcessor 來處理 Bean 中的標註,因此我們需要在配置檔案中作如下宣告來啟用該後處理 Bean,如下所示:
<tx:annotation-driven transaction-manager="transactionManager"/>
與前面相似,transaction-manager、datasource 和 sessionFactory的配置不變,只需將基於<tx>和<aop>名稱空間的配置更換為上述配置即可。
Spring 宣告式事務的本質
就Spring 宣告式事務而言,無論其基於 <tx> 名稱空間的實現還是基於 @Transactional 的實現,其本質都是 Spring AOP 機制的應用:即通過以@Transactional的方式或者XML配置檔案的方式向業務元件中的目標業務方法插入事務增強處理並生成相應的代理物件供應用程式(客戶端)使用從而達到無汙染地新增事務的目的。