[JavaEE - JPA] 3. Spring Framework中的事務管理

前文討論了事務劃分(Transaction Demarcation)在EJB中是如何實現的,本文繼續介紹在spring Framework中是如何完成事務劃分的。

我們已經知道了當採用Container事務類型的時候,事務劃分主要有以下兩種方案(參考這裏):

  1. 使用JTA接口在應用中編碼完成顯式劃分
  2. 在容器的幫助下完成自動劃分

在使用JavaEE的EJB規範時,這兩種方案分別被實現爲BMT以及CMT,關於BMT和CMT在上一篇文章中有比較詳盡的討論(參考這裏)。

那麼對於Spring Framework而言,又是如何來實現上述兩種方案的呢。

Spring Framework的主要優勢之一就是屏蔽了底層容器的很多實現細節,它甚至會在JavaEE的標準之上進行一些封裝,來最大程度地做到平臺無關,容器無關。在使用Spring Framework開發JavaEE應用的時候,對底層容器的依賴並不明顯。因此它並沒有像EJB那樣當採用基於Container的事務類型時,嚴重地依賴於JTA這一規範。它通過提供下面兩種實現方案完成了對於事務的劃分:

  1. 聲明式的事務管理(Declarative Transaction Management)
  2. 編程式的事務管理(Programmatic Transaction Management)

雖然名字叫法不同,但是本質是差不多的。

第一種,聲明式的事務管理,目的就是要幫助開發人員完成事務的自動劃分。這一點和EJB CMT非常類似。第二種,編程式的事務管理,目的就是讓開發人員能夠有辦法自行控制事務應該如何進行。這一點和EJB BMT非常類似。

Spring Framework事務管理概要

在介紹具體的聲明式以及編程式這兩種事務管理方案之前,還是需要簡單介紹一下Spring Framework是如何進行事務管理的。

在傳統的JavaEE中,開發人員一般可以採用兩種事務類型:

  1. Resource-local 本地事務
  2. Global 全局事務 (一般需要應用服務器提供的容器環境,所以也稱爲Container事務)

對於本地事務而言,它沒有辦法針對多個事務性資源,往往只能針對單一的事務資源。而且它也過於底層,對於業務邏輯的侵入性很強,這一點寫過基於JDBC應用的同學都知道,20行代碼裏面可能只有5行是業務相關的,剩下15行都用來處理事務和相關異常了。所以這樣的代碼很難維護,看上去也不那麼優雅。

對於全局事務而言,它克服了本地事務的缺點,能夠處理多個事務性資源(典型的比如數據庫,消息隊列等)。但是它依賴於笨重的JTA(Java Transaction API),這一套和事務相關的API用起來也是讓人叫苦不迭,和JDBC類似,都有太過底層的問題。更重要的是,JTA一般是應用服務器纔會提供的服務,因此使用全局事務的條件也算是比較苛刻。

所以針對以上的種種痛點,Spring Framework建立了一套關於事務的抽象。讓開發人員可以在任何環境中方便地使用事務來管理資源。甚至在一般情況下連應用服務器也不需要了,使用一般的Web容器即可,比如流行的Tomcat(只有在需要同時處理多個事務性資源的情況下,纔可能需要使用應用服務器,但是一般的應用顯然沒有那麼複雜)。

Spring Framework對事務的抽象

Spring Framework通過引入一個名爲事務策略(Transaction Strategy)的概念來建立這個抽象。具體而言,表現爲下面這個接口:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

org.springframework.transaction.PlatformTransactionManager接口本質上是一個服務提供接口(Service Provider Interface, SPI)。這也算是實現系統擴展性的一種經典設計模式了,具體可以參考維基百科。比如我們所熟知的JDBC就是依賴於這一模式,各種數據庫廠商都實現了JDBC SPI中的功能從而使他們的數據庫能夠和Java應用通信。

接口中定義的每個方法都可能會拋出一個名爲TransactionException的異常,這個異常不像是JTA的相關接口中定義的那些異常基本上都是受檢異常(Checked Exception),而TransactionException是一個運行時異常(Runtime Exception)。這也反映出了Spring Framework的設計原則,儘量不給開發人員添亂。畢竟處理事務相關的異常可不是一門輕鬆活。一般而言都是直接拋出去,誰有能力處理交給誰吧。所以這裏將異常定義爲運行時異常默認了開發人員不會處理它,當然如果有能力處理的話加上try…catch語句就好了。

下面簡要介紹一下上述接口中的三個方法:

TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
  • 1
  • 1

這裏有出現了兩個新概念。
接受的參數是TransactionDefinition接口,返回的對象是TransactionStatus接口。

TransactionDefinition接口的文檔中,有這麼一段話:

Interface that defines Spring-compliant transaction properties.
Based on the propagation behavior definitions analogous to EJB CMT attributes.

翻譯一下就是:這個接口定義了Spring兼容的事務屬性。這些屬性類似於EJB CMT中關於事務傳播(Transaction Propagation)行爲的定義。而這個事務傳播實際上就是指的@TransactionAttribute中定義的那些個屬性,諸如MANDATORYREQUIREDREQUIRES_NEW等,詳情可以參考這裏

知道了這一點,再來看看裏面定義了些什麼:

public interface TransactionDefinition {
    // 傳播屬性
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    // 隔離屬性
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
    // 超時屬性
    int TIMEOUT_DEFAULT = -1;

    // 行爲
    int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    String getName();
}
  • 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
  • 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

可見,這個接口裏面定義了各種屬性,同時也定義了獲取一個事務屬性的方法。所以將該接口作爲上述getTransaction方法的參數,目的就很清晰了:根據所描述事務的屬性來獲取具體的事務。就好比傳入一個config對象,得到一個符合該config定義的對象那樣。

然後,方法返回的TransactionStatus又是啥呢:

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這就很明顯了,它和之前介紹過的JTA提供用於實現BMT的UserTransaction相似度很高。它代表的就是一個當前執行線程所關聯的一個具體事務對象。根據TransactionDefinition所定義的事務屬性,這個具體的事務對象可以是一個剛剛創建的(當傳播屬性定義爲PROPAGATION_REQUIRES_NEW時),也可以是複用的一個事務對象(當傳播屬性定義爲PROPAGATION_SUPPORTS或者PROPAGATION_REQUIRED並且確實運行在事務環境中時)。

PlatformTransactionManager接口中剩下的兩個方法commit()rollback()看名字就知道它們怎麼用了,因此不再一一介紹了。

PlatformTransactionManager接口的實現是需要根據具體的環境而被注入到運行時環境中的,比如JTA,JDBC,hibernate等。關於具體的注入方法可以參考Spring Framework的相關文檔,這裏就不再細說。

聲明式的事務管理(Declarative Transaction Management)

值得一體的是,Spring Framework是通過AOP(面向切面編程,Aspect-Oriented Programming)技術來實現聲明式的事務管理的。關於AOP,如果想要了解更多可以參考Wiki

其實Spring Framework在實現聲明式的事務管理時,多少也借鑑了一些EJB CMT的實現方式,取其精華去其糟粕。那麼將它和EJB CMT比較的話,主要有以下幾個方面的提高:

  1. 不再像EJB CMT那樣侷限於JTA。Spring Framework提供的聲明式事務管理能夠運行在幾乎任何主流環境下,比如JTA,JDBC,Hibernate等。開發人員所需要做的只是根據底層環境提供相應的配置即可。
  2. 可以對任何類使用聲明式事務管理,而不像EJB CMT那樣只能對有限的幾種類型,比如@Stateful@Stateless等會話Bean。
  3. 聲明式的回滾規則,比如指定拋出了哪些類型的異常時才發生回滾。這一特性是EJB CMT中不存在的。
  4. 通過AOP技術定製化事務行爲,比如在事務發生回滾的時候執行自定義代碼。而在EJB CMT中對於回滾你能做的僅僅是調用setRollbackOnly()

和EJB CMT比較相近的一點是,它們都在發生了運行時異常(Runtime Exception)時會觸發回滾操作,但是對於受檢異常(Checked Exception)是不會主動回滾的,一般的理解是需要開發人員來處理,在catch語句中顯式地執行回滾操作。

說的這麼好聽,那麼如何使用聲明式的事務管理呢。其實用法很簡單,就兩個步驟(以註解配置方式爲例):
1. 在@Configuration的JavaConfig類中使用@EnableTransactionManagement
2. 在需要使用事務的類或者方法上使用@Transactional

要想理解它是如何實現的,首先需要了解AOP Proxy這一概念。也就是說,在開發人員聲明瞭@Transactional之後,Spring Framework會利用AOP技術生成一個代理對象(Proxy),這個代理對象使用TransactionInterceptor並結合PlatformTransactionManager來實現事務的相關功能。

可以用下面這張圖來表示:

這裏寫圖片描述

在Spring Framework沒有介入之前,只存在上圖中的調用者和目標方法這兩個部分。而在介入後利用AOP以及動態代理技術首先爲方法所在對象創建了一個代理對象,然後根據配置生成各種AOP任務,如上圖中的事務Advisor以及其它類型的Advisor(日誌任務等)。然後在真正發生方法調用的時候,調用的順序如上圖中的數字1-8。在事務Advisor被執行的時候(步驟2)纔會真正創建事務,然後在步驟4執行的是業務邏輯。隨後執行流程開始依次返回,到步驟7發生的時候事務會根據其是否成功而提交或者是回滾。

對於具體的基於XML以及註解的配置方法,可以查看Spring Framework Transaction部分的相關文檔

編程式的事務管理(Programmatic Transaction Management)

Spring Framework提供了兩種方法來支持編程式的事務管理:

  1. TransactionTemplate
  2. PlatformTransactionManager

推薦使用第一種方式。TransactionTemplate在形式上類似於Spring JDBC中提供的JdbcTemplate,封裝了很多模板代碼,讓開發人員可以專注到業務邏輯的開發上。第二種類似於JTA中提供的UserTransaction,但是簡化了部分異常處理。

利用TransactionTemplate完成編程式事務管理

下面是一段使用TransactionTemplate完成編程式事務管理的代碼片段(下面的摘自Spring Framework官方文檔):

public class SimpleService implements Service {
    // 共享的TransactionTemplate實例
    private final TransactionTemplate transactionTemplate;
    // 利用構造器注入將PlatformTransactionManager的實現注入到類中
    public SimpleService(PlatformTransactionManager transactionManager) {
        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }
    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // 此方法中的代碼會在事務上下文中執行
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

使用了回調的風格完成了編程式的事務管理,其中比較關鍵的是TransactionCallback類的匿名實現,它作爲參數傳入到TransactionTemplate的execute方法中。

如果事務相關代碼並沒有返回值,那麼可以使用不帶參數的TransactionCallbackWithoutResult類的匿名實現。

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessExeption ex) {
            // 發生異常時,回滾事務
            status.setRollbackOnly();
        }
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

利用PlatformTransactionManager完成編程式事務管理

可以在獲取到PlatformTransactionManager的實現後通過下面的邏輯完成事務管理:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 只有在編程式事務管理中才能設置事務的名稱
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
    // 執行業務邏輯
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

總結

本文簡要介紹了Spring Framework中的事務管理。它是如何創建出和具體底層環境無關的抽象層,以及如何使用聲明式/編程式來完成事務管理的。介紹的比較簡要,資料參考自Spring Framework官方文檔。如需要具體的配置方法和更多的例子,可以直接參考上述文檔。

原文地址:http://blog.csdn.net/dm_vincent/article/details/52615499

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章