前文討論了事務劃分(Transaction Demarcation)在EJB中是如何實現的,本文繼續介紹在spring Framework中是如何完成事務劃分的。
我們已經知道了當採用Container事務類型的時候,事務劃分主要有以下兩種方案(參考這裏):
- 使用JTA接口在應用中編碼完成顯式劃分
- 在容器的幫助下完成自動劃分
在使用JavaEE的EJB規範時,這兩種方案分別被實現爲BMT以及CMT,關於BMT和CMT在上一篇文章中有比較詳盡的討論(參考這裏)。
那麼對於Spring Framework而言,又是如何來實現上述兩種方案的呢。
Spring Framework的主要優勢之一就是屏蔽了底層容器的很多實現細節,它甚至會在JavaEE的標準之上進行一些封裝,來最大程度地做到平臺無關,容器無關。在使用Spring Framework開發JavaEE應用的時候,對底層容器的依賴並不明顯。因此它並沒有像EJB那樣當採用基於Container的事務類型時,嚴重地依賴於JTA這一規範。它通過提供下面兩種實現方案完成了對於事務的劃分:
- 聲明式的事務管理(Declarative Transaction Management)
- 編程式的事務管理(Programmatic Transaction Management)
雖然名字叫法不同,但是本質是差不多的。
第一種,聲明式的事務管理,目的就是要幫助開發人員完成事務的自動劃分。這一點和EJB CMT非常類似。第二種,編程式的事務管理,目的就是讓開發人員能夠有辦法自行控制事務應該如何進行。這一點和EJB BMT非常類似。
Spring Framework事務管理概要
在介紹具體的聲明式以及編程式這兩種事務管理方案之前,還是需要簡單介紹一下Spring Framework是如何進行事務管理的。
在傳統的JavaEE中,開發人員一般可以採用兩種事務類型:
- Resource-local 本地事務
- 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
中定義的那些個屬性,諸如MANDATORY
,REQUIRED
,REQUIRES_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比較的話,主要有以下幾個方面的提高:
- 不再像EJB CMT那樣侷限於JTA。Spring Framework提供的聲明式事務管理能夠運行在幾乎任何主流環境下,比如JTA,JDBC,Hibernate等。開發人員所需要做的只是根據底層環境提供相應的配置即可。
- 可以對任何類使用聲明式事務管理,而不像EJB CMT那樣只能對有限的幾種類型,比如
@Stateful
,@Stateless
等會話Bean。 - 聲明式的回滾規則,比如指定拋出了哪些類型的異常時才發生回滾。這一特性是EJB CMT中不存在的。
- 通過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提供了兩種方法來支持編程式的事務管理:
TransactionTemplate
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