Spring支持兩種類型的事務(wù)管理:
編程式事務(wù)管理:這意味你通過(guò)編程的方式管理事務(wù),給你帶來(lái)極大的靈活性,但是難維護(hù)。
聲明式事務(wù)管理:這意味著你可以將業(yè)務(wù)代碼和事務(wù)管理分離,你只需用注解和XML配置來(lái)管理事務(wù)。
編程式事務(wù)管理
在 Spring 出現(xiàn)以前,編程式事務(wù)管理對(duì)基于 POJO 的應(yīng)用來(lái)說(shuō)是唯一選擇。用過(guò) Hibernate 的人都知道,我們需要在代碼中顯式調(diào)用beginTransaction()、commit()、rollback()等事務(wù)管理相關(guān)的方法,這就是編程式事務(wù)管理。通過(guò) Spring 提供的事務(wù)管理 API,我們可以在代碼中靈活控制事務(wù)的執(zhí)行。在底層,Spring 仍然將事務(wù)操作委托給底層的持久化框架來(lái)執(zhí)行。
基于底層 API 的編程式事務(wù)管理
根據(jù)PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三個(gè)核心接口,我們完全可以通過(guò)編程的方式來(lái)進(jìn)行事務(wù)管理。
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;
}
}
相應(yīng)的配置文件
<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>
如上所示,我們?cè)陬愔性黾恿藘蓚€(gè)屬性:一個(gè)是 TransactionDefinition 類型的屬性,它用于定義一個(gè)事務(wù);另一個(gè)是 PlatformTransactionManager 類型的屬性,用于執(zhí)行事務(wù)管理操作。
如果方法需要實(shí)施事務(wù)管理,我們首先需要在方法開(kāi)始執(zhí)行前啟動(dòng)一個(gè)事務(wù),調(diào)用PlatformTransactionManager.getTransaction(...) 方法便可啟動(dòng)一個(gè)事務(wù)。創(chuàng)建并啟動(dòng)了事務(wù)之后,便可以開(kāi)始編寫業(yè)務(wù)邏輯代碼,然后在適當(dāng)?shù)牡胤綀?zhí)行事務(wù)的提交或者回滾。
基于 TransactionTemplate 的編程式事務(wù)管理
通過(guò)前面的示例可以發(fā)現(xiàn),這種事務(wù)管理方式很容易理解,但令人頭疼的是,事務(wù)管理的代碼散落在業(yè)務(wù)邏輯代碼中,破壞了原有代碼的條理性,并且每一個(gè)業(yè)務(wù)方法都包含了類似的啟動(dòng)事務(wù)、提交/回滾事務(wù)的樣板代碼。幸好,Spring 也意識(shí)到了這些,并提供了簡(jiǎn)化的方法,這就是 Spring 在數(shù)據(jù)訪問(wèn)層非常常見(jiàn)的模板回調(diào)模式。
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>
TransactionTemplate 的 execute() 方法有一個(gè) TransactionCallback 類型的參數(shù),該接口中定義了一個(gè) doInTransaction() 方法,通常我們以匿名內(nèi)部類的方式實(shí)現(xiàn) TransactionCallback 接口,并在其 doInTransaction() 方法中書寫業(yè)務(wù)邏輯代碼。這里可以使用默認(rèn)的事務(wù)提交和回滾規(guī)則,這樣在業(yè)務(wù)代碼中就不需要顯式調(diào)用任何事務(wù)管理的 API。doInTransaction() 方法有一個(gè)TransactionStatus 類型的參數(shù),我們可以在方法的任何位置調(diào)用該參數(shù)的 setRollbackOnly() 方法將事務(wù)標(biāo)識(shí)為回滾的,以執(zhí)行事務(wù)回滾。
根據(jù)默認(rèn)規(guī)則,如果在執(zhí)行回調(diào)方法的過(guò)程中拋出了未檢查異常,或者顯式調(diào)用了TransacationStatus.setRollbackOnly() 方法,則回滾事務(wù);如果事務(wù)執(zhí)行完成或者拋出了 checked 類型的異常,則提交事務(wù)。
TransactionCallback 接口有一個(gè)子接口 TransactionCallbackWithoutResult,該接口中定義了一個(gè) doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 接口主要用于事務(wù)過(guò)程中不需要返回值的情況。當(dāng)然,對(duì)于不需要返回值的情況,我們?nèi)匀豢梢允褂?TransactionCallback 接口,并在方法中返回任意值即可。
聲明式事務(wù)管理
Spring 的聲明式事務(wù)管理在底層是建立在 AOP 的基礎(chǔ)之上的。其本質(zhì)是對(duì)方法前后進(jìn)行攔截,然后在目標(biāo)方法開(kāi)始之前創(chuàng)建或者加入一個(gè)事務(wù),在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。
聲明式事務(wù)最大的優(yōu)點(diǎn)就是不需要通過(guò)編程的方式管理事務(wù),這樣就不需要在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼,只需在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或通過(guò)等價(jià)的基于標(biāo)注的方式),便可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中。因?yàn)槭聞?wù)管理本身就是一個(gè)典型的橫切邏輯,正是 AOP 的用武之地。Spring 開(kāi)發(fā)團(tuán)隊(duì)也意識(shí)到了這一點(diǎn),為聲明式事務(wù)提供了簡(jiǎn)單而強(qiáng)大的支持。
聲明式事務(wù)管理曾經(jīng)是 EJB 引以為傲的一個(gè)亮點(diǎn),如今 Spring 讓 POJO 在事務(wù)管理方面也擁有了和 EJB 一樣的待遇,讓開(kāi)發(fā)人員在 EJB 容器之外也用上了強(qiáng)大的聲明式事務(wù)管理功能,這主要得益于 Spring 依賴注入容器和 Spring AOP 的支持。依賴注入容器為聲明式事務(wù)管理提供了基礎(chǔ)設(shè)施,使得 Bean 對(duì)于 Spring 框架而言是可管理的;而 Spring AOP 則是聲明式事務(wù)管理的直接實(shí)現(xiàn)者,這一點(diǎn)通過(guò)清單8可以看出來(lái)。
通常情況下,筆者強(qiáng)烈建議在開(kāi)發(fā)中使用聲明式事務(wù),不僅因?yàn)槠浜?jiǎn)單,更主要是因?yàn)檫@樣使得純業(yè)務(wù)代碼不被污染,極大方便后期的代碼維護(hù)。
和編程式事務(wù)相比,聲明式事務(wù)唯一不足地方是,后者的最細(xì)粒度只能作用到方法級(jí)別,無(wú)法做到像編程式事務(wù)那樣可以作用到代碼塊級(jí)別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進(jìn)行事務(wù)管理的代碼塊獨(dú)立為方法等等。
基于TransactionInterceptor 的聲明式事務(wù)管理
基于 TransactionInterceptor 的事務(wù)管理示例配置文件
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="bankServiceTarget"
class="footmark.spring.core.tx.declare.origin.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<bean id="bankService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="bankServiceTarget"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
首先,我們配置了一個(gè) TransactionInterceptor 來(lái)定義相關(guān)的事務(wù)規(guī)則,他有兩個(gè)主要的屬性:一個(gè)是 transactionManager,用來(lái)指定一個(gè)事務(wù)管理器,并將具體事務(wù)相關(guān)的操作委托給它;另一個(gè)是 Properties 類型的 transactionAttributes 屬性,它主要用來(lái)定義事務(wù)規(guī)則,該屬性的每一個(gè)鍵值對(duì)中,鍵指定的是方法名,方法名可以使用通配符,而值就表示相應(yīng)方法的所應(yīng)用的事務(wù)屬性。
指定事務(wù)屬性的取值有較復(fù)雜的規(guī)則,這在 Spring 中算得上是一件讓人頭疼的事。具體的書寫規(guī)則如下:
傳播行為 [,隔離級(jí)別] [,只讀屬性] [,超時(shí)屬性] [不影響提交的異常] [,導(dǎo)致回滾的異常]
1.傳播行為是唯一必須設(shè)置的屬性,其他都可以忽略,Spring為我們提供了合理的默認(rèn)值。
2.傳播行為的取值必須以“PROPAGATION_”開(kāi)頭,具體包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七種取值。
3.隔離級(jí)別的取值必須以“ISOLATION_”開(kāi)頭,具體包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五種取值。
4.如果事務(wù)是只讀的,那么我們可以指定只讀屬性,使用“readOnly”指定。否則我們不需要設(shè)置該屬性。
5.超時(shí)屬性的取值必須以“TIMEOUT_”開(kāi)頭,后面跟一個(gè)int類型的值,表示超時(shí)時(shí)間,單位是秒。
6.不影響提交的異常是指,即使事務(wù)中拋出了這些類型的異常,事務(wù)任然正常提交。必須在每一個(gè)異常的名字前面加上“+”。異常的名字可以是類名的一部分。比如“+RuntimeException”、“+tion”等等。
7.導(dǎo)致回滾的異常是指,當(dāng)事務(wù)中拋出這些類型的異常時(shí),事務(wù)將回滾。必須在每一個(gè)異常的名字前面加上“-”。異常的名字可以是類名的全部或者部分,比如“-RuntimeException”、“-tion”等等。
基于 <tx> 命名空間的聲明式事務(wù)管理(重點(diǎn))
Spring 2.x 引入了 <tx> 命名空間,結(jié)合使用 <aop> 命名空間,帶給開(kāi)發(fā)人員配置聲明式事務(wù)的全新體驗(yàn),配置變得更加簡(jiǎn)單和靈活。另外,得益于 <aop> 命名空間的切點(diǎn)表達(dá)式支持,聲明式事務(wù)也變得更加強(qiáng)大。
<bean id="bankService"
class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<tx:advice id="bankAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
</aop:config>
基于 @Transactional 的聲明式事務(wù)管理(重點(diǎn))
除了基于命名空間的事務(wù)配置方式,Spring 2.x 還引入了基于 Annotation 的方式,具體主要涉及@Transactional 標(biāo)注。@Transactional 可以作用于接口、接口方法、類以及類方法上。當(dāng)作用于類上時(shí),該類的所有 public 方法將都具有該類型的事務(wù)屬性,同時(shí),我們也可以在方法級(jí)別使用該標(biāo)注來(lái)覆蓋類級(jí)別的定義。如清單12所示:
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
return bankDao.transfer(fromId, toId, amount);
}
Spring 使用 BeanPostProcessor 來(lái)處理 Bean 中的標(biāo)注,因此我們需要在配置文件中作如下聲明來(lái)激活該后處理 Bean
<tx:annotation-driven transaction-manager="transactionManager"/>
與前面相似,transaction-manager 屬性的默認(rèn)值是 transactionManager,如果事務(wù)管理器 Bean 的名字即為該值,則可以省略該屬性。
雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 小組建議不要在接口或者接口方法上使用該注解,因?yàn)檫@只有在使用基于接口的代理時(shí)它才會(huì)生效。另外, @Transactional 注解應(yīng)該只被應(yīng)用到 public 方法上,這是由 Spring AOP 的本質(zhì)決定的。如果你在 protected、private 或者默認(rèn)可見(jiàn)性的方法上使用 @Transactional 注解,這將被忽略,也不會(huì)拋出任何異常。
基于 <tx> 命名空間和基于 @Transactional 的事務(wù)聲明方式各有優(yōu)缺點(diǎn)。基于 <tx> 的方式,其優(yōu)點(diǎn)是與切點(diǎn)表達(dá)式結(jié)合,功能強(qiáng)大。利用切點(diǎn)表達(dá)式,一個(gè)配置可以匹配多個(gè)方法,而基于 @Transactional 的方式必須在每一個(gè)需要使用事務(wù)的方法或者類上用 @Transactional 標(biāo)注,盡管可能大多數(shù)事務(wù)的規(guī)則是一致的,但是對(duì) @Transactional 而言,也無(wú)法重用,必須逐個(gè)指定。另一方面,基于 @Transactional 的方式使用起來(lái)非常簡(jiǎn)單明了,沒(méi)有學(xué)習(xí)成本。開(kāi)發(fā)人員可以根據(jù)需要,任選其中一種使用,甚至也可以根據(jù)需要混合使用這兩種方式。
編程式總結(jié):
1.基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 編程式事務(wù)管理是 Spring 提供的最原始的方式,通常我們不會(huì)這么寫,但是了解這種方式對(duì)理解 Spring 事務(wù)管理的本質(zhì)有很大作用。
2.基于 TransactionTemplate 的編程式事務(wù)管理是對(duì)上一種方式的封裝,使得編碼更簡(jiǎn)單、清晰。
3.基于 TransactionInterceptor 的聲明式事務(wù)是 Spring 聲明式事務(wù)的基礎(chǔ),通常也不建議使用這種方式,但是與前面一樣,了解這種方式對(duì)理解 Spring 聲明式事務(wù)有很大作用。
注解式總結(jié)
1.基于 <tx> 和 <aop> 命名空間的聲明式事務(wù)管理是目前推薦的方式,其最大特點(diǎn)是與 Spring AOP 結(jié)合緊密,可以充分利用切點(diǎn)表達(dá)式的強(qiáng)大支持,使得管理事務(wù)更加靈活。
2.基于 @Transactional 的方式將聲明式事務(wù)管理簡(jiǎn)化到了極致。開(kāi)發(fā)人員只需在配置文件中加上一行啟用相關(guān)后處理 Bean 的配置,然后在需要實(shí)施事務(wù)管理的方法或者類上使用 @Transactional 指定事務(wù)規(guī)則即可實(shí)現(xiàn)事務(wù)管理,而且功能也不必其他方式遜色。