程序員筆記第一彈:Spring 事務管理機制概述

程序員筆記第一彈:Spring 事務管理機制概述

一. Spring 事務概述

一般而言,用戶的每次請求都對應一個業務邏輯方法,並且每個業務邏輯方法往往具有邏輯上的原子性。此外,一個業務邏輯方法往往包括一系列數據庫原子訪問操作,並且這些數據庫原子訪問操作應該綁定成一個整體,即要麼全部執行,要麼全部不執行,通過這種方式我們可以保證數據庫的完整性,這就是事務。總的來說,事務是一個不可分割操作序列,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另一種一致性狀態。

但是,在使用傳統的事務編程策略時,程序代碼必然和具體的事務操作代碼耦合,如下所示:

// JDBC事務
Connection conn = getConnection();
conn.setAutoCommit(false);
...
// 業務實現
...
if 正常
conn.commit();
if 失敗
conn.rollback();
// Hibernate事務
Session s = getSession();
Transaction tx = s.beginTransaction();
...

// 業務實現
...
if 正常
tx.commit();
if 失敗
tx.rollback();

因此,當應用需要在不同的事務策略之間切換時,開發者必須手動修改程序代碼。使用Spring事務管理策略,就可以避免這種尷尬。因為Spring的事務管理不需與任何特定的事務API耦合,並且其提供了兩種事務管理方式:編程式事務管理和聲明式事務管理。對不同的持久層訪問技術,編程式事務提供一致的事務編程風格,通過模板化的操作一致性地管理事務;而聲明式事務基於Spring AOP實現,卻並不需要程序開發者成為AOP專家,亦可輕易使用Spring的聲明式事務管理。

二. Spring 事務管理 API

Spring 框架中,最重要的事務管理的 API 有三個:TransactionDefinition、PlatformTransactionManager 和 TransactionStatus。 所謂事務管理,實質上就是按照給定的事務規則來執行提交或者回滾操作。其中,“給定的事務規則”是用 TransactionDefinition 表示的,“按照……來執行提交或者回滾操作”是用 PlatformTransactionManager 表示的,而 TransactionStatus 可以看作代表事務本身。

2.1、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所定義的事務規則所決定。

2.2、TransactionDefinition 接口

TransactionDefinition 接口用於定義一個事務的規則,它包含了事務的一些靜態屬性,比如:事務傳播行為、超時時間等。同時,Spring 還為我們提供了一個默認的實現類:DefaultTransactionDefinition,該類適用於大多數情況。如果該類不能滿足需求,可以通過實現 TransactionDefinition 接口來實現自己的事務定義。

TransactionDefinition接口包含與事務屬性相關的方法,如下所示:

public interface TransactionDefinition{
int getIsolationLevel();
int getPropagationBehavior();
int getTimeout();
boolean isReadOnly();
}

TransactionDefinition 接口只提供了獲取屬性的方法,而沒有提供相關設置屬性的方法。因為,事務屬性的設置完全是程序員控制的,因此程序員可以自定義任何設置屬性的方法,而且保存屬性的字段也沒有任何要求。唯一的要求的是,Spring 進行事務操作的時候,通過調用以上接口提供的方法必須能夠返回事務相關的屬性取值。例如,TransactionDefinition 接口的默認的實現類 —— DefaultTransactionDefinition 就同時定義了一系列屬性設置和獲取方法。

TransactionDefinition 接口定義的事務規則包括:事務隔離級別、事務傳播行為、事務超時、事務的只讀屬性和事務的回滾規則,下面我們一一詳細介紹。

2.3、事務隔離級別

所謂事務的隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:

TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,該級別就是 TransactionDefinition.ISOLATION_READ_COMMITTED;

TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據,該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別;

TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。

TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的數據滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。

TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是,這將嚴重影響程序的性能,通常情況下也不會用到該級別。

2.4、 事務傳播行為

所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。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 啟動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。另外,外部事務的回滾也會導致嵌套子事務的回滾。

2.5、 事務超時

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

2.6、 事務的只讀屬性

事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如數據源、 JMS 資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌為只讀的,以提高事務處理的性能。在 TransactionDefinition接口中,以 boolean 類型來表示該事務是否只讀。

2.7、 事務的回滾規則

通常情況下,如果在事務中拋出了未檢查異常(繼承自 RuntimeException 的異常),則默認將回滾事務。如果沒有拋出任何異常,或者拋出了已檢查異常,則仍然提交事務。這通常也是大多數開發者希望的處理方式,也是 EJB 中的默認處理方式。但是,我們可以根據需要人為控制事務在拋出某些未檢查異常時任然提交事務,或者在拋出某些已檢查異常時回滾事務。

2.8、TransactionStatus 接口

PlatformTransactionManager.getTransaction(…) 方法返回一個 TransactionStatus 對象,該對象可能代表一個新的或已經存在的事務(如果在當前調用堆棧有一個符合條件的事務)。TransactionStatus 接口提供了一個簡單的控制事務執行和查詢事務狀態的方法。該接口的源代碼如下:

public interface TransactionStatus{
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
}

三. Spring 編程式事務管理

在 Spring 出現以前,編程式事務管理對基於 POJO 的應用來說是唯一選擇。用過 Hibernate 的人都知道,我們需要在代碼中顯式調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理。通過 Spring 提供的事務管理 API,我們可以在代碼中靈活控制事務的執行。在底層,Spring 仍然將事務操作委託給底層的持久化框架來執行。

3.1、基於底層 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>
<property>
<property>
<property>
<bean>
<property>
/<bean>
/<property>
/<bean>

如上所示,我們在BankServiceImpl類中增加了兩個屬性:一個是 TransactionDefinition 類型的屬性,它用於定義事務的規則;另一個是 PlatformTransactionManager 類型的屬性,用於執行事務管理操作。如果一個業務方法需要添加事務,我們首先需要在方法開始執行前調用PlatformTransactionManager.getTransaction(…) 方法啟動一個事務;創建並啟動了事務之後,便可以開始編寫業務邏輯代碼,然後在適當的地方執行事務的提交或者回滾。

3.2、基於 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> 

<property>
<property>
/<bean>

TransactionTemplate 的 execute() 方法有一個 TransactionCallback 類型的參數,該接口中定義了一個 doInTransaction() 方法,通常我們以匿名內部類的方式實現 TransactionCallback 接口,並在其 doInTransaction() 方法中書寫業務邏輯代碼。這裡可以使用默認的事務提交和回滾規則,這樣在業務代碼中就不需要顯式調用任何事務管理的 API。doInTransaction() 方法有一個TransactionStatus 類型的參數,我們可以在方法的任何位置調用該參數的 setRollbackOnly() 方法將事務標識為回滾的,以執行事務回滾。

此外,TransactionCallback 接口有一個子接口 TransactionCallbackWithoutResult,該接口中定義了一個 doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 接口主要用於事務過程中不需要返回值的情況。當然,對於不需要返回值的情況,我們仍然可以使用 TransactionCallback 接口,並在方法中返回任意值即可。

Spring 的聲明式事務管理是建立在 Spring AOP 機制之上的,其本質是對目標方法前後進行攔截,並在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。

聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或通過等價的基於標註的方式),便可以將事務規則應用到業務邏輯中。總的來說,聲明式事務得益於 Spring IoC容器 和 Spring AOP 機制的支持:IoC容器為聲明式事務管理提供了基礎設施,使得 Bean 對於 Spring 框架而言是可管理的;而由於事務管理本身就是一個典型的橫切邏輯(正是 AOP 的用武之地),因此 Spring AOP 機制是聲明式事務管理的直接實現者。

顯然,聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受汙染,一個普通的POJO對象,只要在XML文件中配置或者添加註解就可以獲得完全的事務支持。因此,通常情況下,筆者強烈建議在開發中使用聲明式事務,不僅因為其簡單,更主要是因為這樣使得純業務代碼不被汙染,極大方便後期的代碼維護。

4.1、基於 命名空間的聲明式事務管理

Spring 2.x 引入了 命名空間,結合使用 命名空間,帶給開發人員配置聲明式事務的全新體驗,配置變得更加簡單和靈活。總的來說,開發者只需基於命名空間在XML中進行簡答配置便可實現聲明式事務管理。下面基於使用Hibernate事務管理的配置文件:

 

<bean> destroy-method="close">

<property>
<value>com.mysql.jdbc.Driver/<value>
/<property>
<property>
<value>jdbc:mysql://localhost:3306/ssh/<value>
/<property>
<property>
<value>root/<value>
/<property>
<property>
<value>root/<value>
/<property>
/<bean>

<bean> class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<property>

<property>
<list>
<value>cn.edu.tju.rico.model.entity.User/<value>
<value>cn.edu.tju.rico.model.entity.Log/<value>
/<list>
/<property>

<property>
<props>

<prop>org.hibernate.dialect.MySQLDialect/<prop>

<prop>true/<prop>

<prop>true/<prop>

<prop>update/<prop>

/<props>
/<property>
/<bean>

<bean> class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property>
/<bean>

<config>

<pointcut> id="bussinessService" />

<advisor>
/<pointcut>/<config>

<advice>
<attributes>

<method>

<method> isolation="DEFAULT" />
/<method>/<attributes>
/<advice>

事實上,Spring配置文件中關於事務的配置總是由三個部分組成,即:DataSource、TransactionManager和代理機制三部分,無論哪種配置方式,一般變化的只是代理機制這部分。其中,DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用hibernate進行數據訪問時,DataSource實際為SessionFactory,TransactionManager的實現為 HibernateTransactionManager。如下圖所示:

程序員筆記第一彈:Spring 事務管理機制概述

4.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);
}

Spring 使用 BeanPostProcessor 來處理 Bean 中的標註,因此我們需要在配置文件中作如下聲明來激活該後處理 Bean,如下所示:

<annotation-driven>

與前面相似,transaction-manager、datasource 和 sessionFactory的配置不變,只需將基於命名空間的配置更換為上述配置即可。

4.3、Spring 聲明式事務的本質

就Spring 聲明式事務而言,無論其基於 命名空間的實現還是基於 @Transactional 的實現,其本質都是 Spring AOP 機制的應用:即通過以@Transactional的方式或者XML配置文件的方式向業務組件中的目標業務方法插入事務增強處理並生成相應的代理對象供應用程序(客戶端)使用從而達到無汙染地添加事務的目的。如下圖所示:

程序員筆記第一彈:Spring 事務管理機制概述


分享到:


相關文章: