管理兩種方式
spring支持編程式事務管理和聲明式事務管理兩種方式。
- 編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對于編程式事務管理,spring推薦使用TransactionTemplate。
- 聲明式事務管理建立在AOP之上的。其本質(zhì)是對方法前后進行攔截,然后在目標方法開始之前創(chuàng)建或者加入一個事務,在執(zhí)行完目標方法之后根據(jù)執(zhí)行情況提交或者回滾事務。
聲明式事務最大的優(yōu)點就是不需要通過編程的方式管理事務,這樣就不需要在業(yè)務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規(guī)則聲明(或通過基于@Transactional注解的方式),便可以將事務規(guī)則應用到業(yè)務邏輯中。
顯然聲明式事務管理要優(yōu)于編程式事務管理,這正是spring倡導的非侵入式的開發(fā)方式。聲明式事務管理使業(yè)務代碼不受污染,一個普通的POJO對象,只要加上注解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,后者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立為方法等等。
聲明式事務管理也有兩種常用的方式,一種是基于tx和aop名字空間的xml配置文件,另一種就是基于@Transactional注解。顯然基于注解的方式更簡單易用,更清爽。
聲明式事務的使用技巧
1、@Transactional可以作用于接口、接口方法、類、類方法上,當作用到類時,該類下所有public方法都將具有該類型的事務屬性,同時,也可以在方法級別使用該注解來覆蓋類級別的定義。Spring的建議是在具體的實現(xiàn)類和類方法使用@Transactional注解,而不是使用在接口上。因為注解不能繼承,不能被基于接口的代理類所識別,注解失效。
2、聲明式事務管理默認只對非檢查型異常unchecked Exception進行回滾,也就是對RuntimeException異常以及它的子類進行回滾操作。
如果需要讓checked Exception也進行回滾,需加上@Transactional(rollbackFor=Exception.class)、
如果需要讓unchecked Exception不進行回滾,需加上@Transactional(notRollbackFor=Exception.class)
3、在Springboot使用聲明式事務需要在Application啟動類加入@EnableTransactionManagement注解,相當于Spring的自動掃描
四、聲明式事務的常用配置
| 參 數(shù) 名 稱 | 功 能 描 述 |
|---|---|
| readOnly | 該屬性用于設置當前事務是否為只讀事務,設置為true表示只讀,false則表示可讀寫,默認值為false。例如:@Transactional(readOnly=true) |
| rollbackFor | 該屬性用于設置需要進行回滾的異常類數(shù)組,當方法中拋出指定異常數(shù)組中的異常時,則進行事務回滾。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
| rollbackForClassName | 該屬性用于設置需要進行回滾的異常類名稱數(shù)組,當方法中拋出指定異常名稱數(shù)組中的異常時,則進行事務回滾。例如:指定單一異常類名稱@Transactional(rollbackForClassName=”RuntimeException”)指定多個異常類名稱:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
| noRollbackFor | 該屬性用于設置不需要進行回滾的異常類數(shù)組,當方法中拋出指定異常數(shù)組中的異常時,不進行事務回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
| noRollbackForClassName | 該屬性用于設置不需要進行回滾的異常類名稱數(shù)組,當方法中拋出指定異常名稱數(shù)組中的異常時,不進行事務回滾。例如:指定單一異常類名稱:@Transactional(noRollbackForClassName=”RuntimeException”)指定多個異常類名稱:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
| propagation | 該屬性用于設置事務的傳播行為。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
| isolation | 該屬性用于設置底層數(shù)據(jù)庫的事務隔離級別,事務隔離級別用于處理多事務并發(fā)的情況,通常使用數(shù)據(jù)庫的默認隔離級別即可,基本不需要進行設置 |
| timeout | 該屬性用于設置事務的超時秒數(shù),默認值為-1表示永不超時 |
spring事務特性
spring所有的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口其中TransactionDefinition接口定義以下特性:
事務隔離級別
隔離級別是指若干個并發(fā)的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:
- TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數(shù)據(jù)庫的默認隔離級別。對大部分數(shù)據(jù)庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數(shù)據(jù)。該級別不能防止臟讀,不可重復讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上并沒有此級別。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經(jīng)提交的數(shù)據(jù)。該級別可以防止臟讀,這也是大多數(shù)情況下的推薦值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重復執(zhí)行某個查詢,并且每次返回的記錄都相同。該級別可以防止臟讀和不可重復讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執(zhí)行,這樣事務之間就完全不可能產(chǎn)生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經(jīng)存在,此時有若干選項可以指定一個事務性方法的執(zhí)行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創(chuàng)建一個新的事務。這是默認值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:創(chuàng)建一個新的事務,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續(xù)運行。(注意這里,不要采坑)
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
- TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創(chuàng)建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED。
事務超時
所謂事務超時,就是指一個事務所允許執(zhí)行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以int 的值來表示超時時間,其單位是秒。
默認設置為底層事務系統(tǒng)的超時值,如果底層數(shù)據(jù)庫事務系統(tǒng)沒有設置超時值,那么就是none,沒有超時限制。
事務只讀屬性
只讀事務用于客戶代碼只讀但不修改數(shù)據(jù)的情形,只讀事務用于特定情景下的優(yōu)化,比如使用Hibernate的時候。
“只讀事務”并不是一個強制選項,它只是一個“暗示”,提示數(shù)據(jù)庫驅(qū)動程序和數(shù)據(jù)庫系統(tǒng),這個事務并不包含更改數(shù)據(jù)的操作,那么JDBC驅(qū)動程序和數(shù)據(jù)庫就有可能根據(jù)這種情況對該事務進行一些特定的優(yōu)化,比方說不安排相應的數(shù)據(jù)庫鎖,以減輕事務對數(shù)據(jù)庫的壓力,畢竟事務也是要消耗數(shù)據(jù)庫的資源的。
但是你非要在“只讀事務”里面修改數(shù)據(jù),也并非不可以,只不過對于數(shù)據(jù)一致性的保護不像“讀寫事務”那樣保險而已。
因此,“只讀事務”僅僅是一個性能優(yōu)化的推薦配置而已,并非強制你要這樣做不可。
spring事務回滾規(guī)則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內(nèi)拋出異常。spring事務管理器會捕捉任何未處理的異常,然后依據(jù)規(guī)則決定是否回滾拋出異常的事務。默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調(diào)用完setRollbackOnly()后你所能執(zhí)行的唯一操作就是回滾。
示例:基于注解的聲明式事務管理配置@Transactional
spring.xml
<!-- mybatis config -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation">
<value>classpath:mybatis-config.xml</value>
</property>
</bean>
<!-- mybatis mappers, scanned automatically -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage">
<value> com.baobao.persistence.test </value>
</property>
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!-- 配置spring的PlatformTransactionManager,名字為默認值 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟事務控制的注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
添加tx名字空間
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數(shù)據(jù)源與DataSourceTransactionManager引用的數(shù)據(jù)源一致即可,否則事務管理會不起作用。
@Transactional注解
使用@Transactional時,可以指定如下屬性:
a、isolation:用于指定事務的隔離級別。默認為底層事務的隔離級別。
b、noRollbackFor:指定遇到指定異常時強制不回滾事務。
c、noRollbackForClassName:指定遇到指定多個異常時強制不回滾事務。該屬性可以指定多個異常類名。
d、propagation:指定事務的傳播屬性。
e、readOnly:指定事務是否只讀。
f、rollbackFor:指定遇到指定異常時強制回滾事務。
g、rollbackForClassName:指定遇到指定多個異常時強制回滾事務。該屬性可以指定多個異常類名。
h、timeout:指定事務的超時時長。**
事務可回滾要注意點*
1、被注解的必須是public
@Transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義。
雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這只有在使用基于接口的代理時它才會生效。另外, @Transactional 注解應該只被應用到 public 方法上,這是由 Spring AOP 的本質(zhì)決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 注解,這將被忽略,也不會拋出任何異常。
poxy-target-class="true"表示使用CGLib動態(tài)代理技術織入增強。不過即使proxy-target-class設置為false,如果目標類沒有聲明接口,則spring將自動使用CGLib動態(tài)代理。</pre>
2、rollbackFor和noRollbackFor
需我們指定方式來讓事務回滾 :
要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常})
如果讓unchecked例外不回滾:@Transactional(notRollbackFor=RunTimeException.class)
(關于rollbackFor配置的經(jīng)歷)
當?shù)腡ransactional中配置rollbackFor = Exception.class時,拋出RuntimeException時是會回滾的。但是如果是Unchecked Exceptions則不會回滾。
于是查看Spring的Transactional的API文檔,發(fā)現(xiàn)下面這段:
If no rules are relevant to the exception, it will be treated like DefaultTransactionAttribute (rolling back on runtime exceptions).
后面又試了下發(fā)現(xiàn),如果不添加rollbackFor等屬性,Spring碰到Unchecked Exceptions都會回滾,不僅是RuntimeException,也包括Error。
3、來自外部的方法調(diào)用才會被AOP代理捕獲
默認情況下,只有來自外部的方法調(diào)用才會被AOP代理捕獲,也就是,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會引起事務行為,即使被調(diào)用方法使用@Transactional注解進行修飾。(我碰到后,只有重構(gòu)代碼再引入一層service解決)
示例一:(不要rollbackFor,unchecked異常,可以回滾)
@Autowired
private MyBatisDao dao;
@Transactional
@Override public void insert(Test test) {
dao.insert(test); throw new RuntimeException("test");
//拋出unchecked異常,觸發(fā)事物,回滾
}
示例二:(noRollbackFor使用場景)
@Transactional(noRollbackFor=RuntimeException.class)
@Override public void insert(Test test) {
dao.insert(test);
//拋出unchecked異常,觸發(fā)事物,noRollbackFor=RuntimeException.class,不回滾
throw new RuntimeException("test");
}
示例三:當作用于類上時,該類所有 public 方法將都具有該類型的事務屬性。
@Transactional
public class MyBatisServiceImpl implements MyBatisService {
@Autowired private MyBatisDao dao;
@Override public void insert(Test test) {
dao.insert(test); //拋出unchecked異常,觸發(fā)事物,回滾
throw new RuntimeException("test");
}
}
示例四:propagation=Propagation.NOT_SUPPORTED
@Transactional(propagation=Propagation.NOT_SUPPORTED)
@Override public void insert(Test test) {
//事物傳播行為是PROPAGATION_NOT_SUPPORTED,以非事務方式運行,不會存入數(shù)據(jù)庫
dao.insert(test);
}
事物注解方式: @Transactional
當標于類前時, 標示類中所有方法都進行事物處理 , 例子:
@Transactional
public class TestServiceBean implements TestService {
}
當類中某些方法不需要事物時:
@Transactional
public class TestServiceBean implements TestService {
private TestDao dao;
public void setDao(TestDao dao) {
this.dao = dao;
}
@Transactional(propagation =Propagation.NOT_SUPPORTED)
public List getAll() {
return null;
}
}
事物傳播行為介紹:
@Transactional(propagation=Propagation.REQUIRED)
如果有事務, 那么加入事務, 沒有的話新建一個(默認情況下)@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不為這個方法開啟事務@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事務,都創(chuàng)建一個新的事務,原來的掛起,新的執(zhí)行完畢,繼續(xù)執(zhí)行老的事務@Transactional(propagation=Propagation.MANDATORY)
必須在一個已有的事務中執(zhí)行,否則拋出異常@Transactional(propagation=Propagation.NEVER)
必須在一個沒有的事務中執(zhí)行,否則拋出異常(與Propagation.MANDATORY相反)@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean調(diào)用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務.
事物超時設置:
@Transactional(timeout=30) //默認是30秒
事務隔離級別:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
讀取未提交數(shù)據(jù)(會出現(xiàn)臟讀, 不可重復讀) 基本不使用@Transactional(isolation = Isolation.READ_COMMITTED)
讀取已提交數(shù)據(jù)(會出現(xiàn)不可重復讀和幻讀)@Transactional(isolation = Isolation.REPEATABLE_READ)
可重復讀(會出現(xiàn)幻讀)@Transactional(isolation = Isolation.SERIALIZABLE)
串行化
MYSQL:默認為REPEATABLE_READ級別
SQLSERVER:默認為READ_COMMITTED
臟讀 : 一個事務讀取到另一事務未提交的更新數(shù)據(jù)。
不可重復讀 : 在同一事務中, 多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同。后續(xù)讀取可以讀到另一事務已提交的更新數(shù)據(jù)。
可重復讀:在同一事務中多次讀取數(shù)據(jù)時, 能夠保證所讀數(shù)據(jù)一樣, 也就是后續(xù)讀取不能讀到另一事務已提交的更新數(shù)據(jù)。
幻讀 : 一個事務讀到另一個事務已提交的insert數(shù)據(jù)。
@Transactional注解中常用參數(shù)說明
| 參數(shù)名稱 | 功能描述 |
|---|---|
| readOnly | 該屬性用于設置當前事務是否為只讀事務,設置為true表示只讀,false則表示可讀寫,默認值為false。例如:@Transactional(readOnly=true) |
| rollbackFor | 該屬性用于設置需要進行回滾的異常類數(shù)組,當方法中拋出指定異常數(shù)組中的異常時,則進行事務回滾。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class);指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
| rollbackForClassName | 該屬性用于設置需要進行回滾的異常類名稱數(shù)組,當方法中拋出指定異常名稱數(shù)組中的異常時,則進行事務回滾。例如:指定單一異常類名稱@Transactional(rollbackForClassName="RuntimeException");指定多個異常類名稱:@Transactional(rollbackForClassName{"RuntimeException","Exception"}) |
| noRollbackFor | 該屬性用于設置不需要進行回滾的異常類數(shù)組,當方法中拋出指定異常數(shù)組中的異常時,不進行事務回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class);指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
| noRollbackForClassName | 該屬性用于設置不需要進行回滾的異常類名稱數(shù)組,當方法中拋出指定異常名稱數(shù)組中的異常時,不進行事務回滾。例如:指定單一異常類名稱@Transactional(noRollbackForClassName="RuntimeException") |
指定多個異常類名稱:
@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
| 參數(shù)名稱 | 功能描述 |
|---|---|
| propagation | 該屬性用于設置事務的傳播行為,具體取值可參考表6-7。例@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
| isolation | 該屬性用于設置底層數(shù)據(jù)庫的事務隔離級別,事務隔離級別用于處理多事務并發(fā)的情況,通常使用數(shù)據(jù)庫的默認隔離級別即可,基本不需要進行設置 |
| timeout | 該屬性用于設置事務的超時秒數(shù),默認值為-1表示永不超時 |
注意的幾點:
1、@Transactional 只能被應用到public方法上, 對于其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
2、用 spring 事務管理器,由spring來負責數(shù)據(jù)庫的打開,提交,回滾.默認遇到運行期例外(throw new RuntimeException("注釋");)會回滾,即遇到不受檢查(unchecked)的例外時回滾;而遇到需要捕獲的例外(throw new Exception("注釋");)不會回滾,即遇到受檢查的例外(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務回滾要想所有異常都回滾。
要加上 @Transactional( rollbackFor={Exception.class,其它異常}) 。如果讓unchecked例外不回滾:@Transactional(notRollbackFor=RunTimeException.class)如下:
@Transactional(rollbackFor=Exception.class)
//指定回滾,遇到異常Exception時回滾
public void methodName() {
throw new Exception("注釋");
}
@Transactional(noRollbackFor=Exception.class)
//指定不回滾,遇到運行期例外(throw new RuntimeException("注釋");)會回滾
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("注釋");
}
3、@Transactional 注解應該只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯, 但是這個被注解的方法將不會展示已配置的事務設置。
4、@Transactional 注解可以被應用于接口定義和接口方法、類定義和類的 public 方法上。然而,請注意僅僅 @Transactional 注解的出現(xiàn)不足于開啟事務行為,它僅僅 是一種元數(shù)據(jù),能夠被可以識別 @Transactional 注解和上述的配置適當?shù)木哂惺聞招袨榈腷eans所使用。上面的例子中,其實正是 元素的出現(xiàn) 開啟 了事務行為。
5、Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現(xiàn)的任何接口上。你當然可以在接口上使用 @Transactional 注解,但是這將只能當你設置了基于接口的代理時它才生效。因為注解是不能繼承的,這就意味著如果你正在使用基于類的代理時,那么事務的設置將不能被基于類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認為嚴重的)。因此,請接受Spring團隊的建議并且在具體的類上使用 @Transactional 注解。
嵌套事務示例
-
Propagation.REQUIRED+Propagation.REQUIRES_NEW
@Service
public class ServiceAImpl implements ServiceA {
@Autowired private ServiceB serviceB;
@Autowired private VcSettleMainMapper vcSettleMainMapper;
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
String id = IdGenerator.generatePayId("A");
VcSettleMain vc = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("ServiceAImpl VcSettleMain111:" + vc);
serviceB.methodB();
VcSettleMain vc2 = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc2);
System.out.println("ServiceAImpl VcSettleMain22222:" + vc2);
}
private VcSettleMain buildModel(String id) {
VcSettleMain vc = new VcSettleMain();
vc.setBatchNo(id);
vc.setCreateBy("dxz");
vc.setCreateTime(LocalDateTime.now());
vc.setTotalCount(11L);
vc.setTotalMoney(BigDecimal.ZERO);
vc.setState("5"); return vc;
}
}
ServiceB
@Service public class ServiceBImpl implements ServiceB {
@Autowired private VcSettleMainMapper vcSettleMainMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public void methodB() {
String id = IdGenerator.generatePayId("B");
VcSettleMain vc = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("---ServiceBImpl VcSettleMain:" + vc);
}
}
controller
@RestController
@RequestMapping("/demo")
public class Demo1 {
@Autowired
private ServiceA serviceA;
/** * 嵌套事務測試 */
@PostMapping(value = "/test1")
public String methodA() throws Exception {
serviceA.methodA();
return "ok";
}
}
結(jié)果:

看數(shù)據(jù)庫表記錄:

這種情況下, 因為 ServiceB#methodB 的事務屬性為 PROPAGATION_REQUIRES_NEW,ServiceB是一個獨立的事務,與外層事務沒有任何關系。如果ServiceB執(zhí)行失敗(上面示例中讓ServiceB的id為已經(jīng)存在的值),ServiceA的調(diào)用出會拋出異常,導致ServiceA的事務回滾。
并且, 在 ServiceB#methodB 執(zhí)行時 ServiceA#methodA 的事務已經(jīng)掛起了 (關于事務掛起的內(nèi)容已經(jīng)超出了本文的討論范圍)。
-
Propagation.REQUIRED+Propagation.REQUIRED
//ServiceA //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
//ServiceB //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...
}
}
--“1”可插入,“2”可插入,“3”不可插入:
結(jié)果是“1”,“2”,“3”都不能插入,“1”,“2”被回滾。
--“1”可插入,“2”不可插入,“3”可插入:
結(jié)果是“1”,“2”,“3”都不能插入,“1”,“2”被回滾。
-
Propagation.REQUIRED+無事務注解
//ServiceA //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
//ServiceB //...
//沒有加事務注解
public void methodB(String id) {
//..
}
}
--“1”可插入,“2”可插入,“3”不可插入:
結(jié)果是“1”,“2”,“3”都不能插入,“1”,“2”被回滾。
2.4、內(nèi)層事務被try-catch:
try-catch + Propagation.REQUIRED+Propagation.REQUIRED
//ServiceA //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
try {
serviceB.methodB(id);
} catch (Exception e) {
System.out.println("內(nèi)層事務出錯啦。");
}
}
//ServiceB //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...
}
--“1”可插入,“2”不可插入,“3”可插入:
結(jié)果是“1”,“2”,“3”都不能插入,“1”被回滾。
事務設置為Propagation.REQUIRED時,如果內(nèi)層方法拋出Exception,外層方法中捕獲Exception但是并沒有繼續(xù)向外拋出,最后出現(xiàn)“Transaction rolled back because it has been marked as rollback-only”的錯誤。外層的方法也將會回滾。
其原因是:內(nèi)層方法拋異常返回時,transacation被設置為rollback-only了,但是外層方法將異常消化掉,沒有繼續(xù)向外拋,那么外層方法正常結(jié)束時,transaction會執(zhí)行commit操作,但是transaction已經(jīng)被設置為rollback-only了。所以,出現(xiàn)“Transaction rolled back because it has been marked as rollback-only”錯誤。
-
try-catch + Propagation.REQUIRED+Propagation.NESTED
//ServiceA //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
try {
serviceB.methodB(id);
} catch (Exception e) {
System.out.println("內(nèi)層事務出錯啦。");
}
}
//ServiceB //...
@Transactional(propagation = Propagation.NESTED, readOnly = false)
public void methodB(String id) {
//...
}
--“1”可插入,“2”不可插入,“3”可插入:
結(jié)果是“1”,“3"記錄插入成功,“2”記錄插入失敗。
說明:
當內(nèi)層配置成 PROPAGATION_NESTED, 此時兩者之間又將如何協(xié)作呢? 從 Juergen Hoeller 的原話中我們可以找到答案, ServiceB#methodB 如果 rollback, 那么內(nèi)部事務(即 ServiceB#methodB) 將回滾到它執(zhí)行前的 SavePoint(注意, 這是本文中第一次提到它, 潛套事務中最核心的概念), 而外部事務(即 ServiceA#methodA) 可以有以下兩種處理方式:
- 內(nèi)層失敗,外層調(diào)用其它分支。
ServiceA {
// 事務屬性配置為 PROPAGATION_REQUIRED
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 執(zhí)行其他業(yè)務, 如 ServiceC.methodC();
}
}
這種方式也是潛套事務最有價值的地方, 它起到了分支執(zhí)行的效果, 如果ServiceB.methodB 失敗, 那么執(zhí)行 ServiceC.methodC(), 而 ServiceB.methodB 已經(jīng)回滾到它執(zhí)行之前的 SavePoint, 不會產(chǎn)生臟數(shù)據(jù)(相當于此方法從未執(zhí)行過), 該特性可用在某些特殊的業(yè)務中, PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點。
- 代碼不做任何修改, 那么如果內(nèi)部事務(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滾到它執(zhí)行之前的 SavePoint(在任何情況下都會如此), 外部事務(即 ServiceA#methodA) 將根據(jù)具體的配置決定自己是 commit 還是 rollback。
三、嵌套事務總結(jié)
使用嵌套事務的場景有兩點需求:
- 需要事務BC與事務AD一起commit,即:作為事務AD的子事務,事務BC只有在事務AD成功commit時(階段3成功)才commit。這個需求簡單稱之為“聯(lián)合成功”。這一點PROPAGATION_NESTED和PROPAGATION_REQUIRED可以做到。
- 需要事務BC的rollback不(無條件的)影響事務AD的commit。這個需求簡單稱之為“隔離失敗”。這一點PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW可以做到。
分解下,可知PROPAGATION_NESTED的特殊性有:
1、使用PROPAGATION_REQUIRED滿足需求1,但子事務BC的rollback會無條件地使父事務AD也rollback,不能滿足需求2。即使對子事務進行了try-catch,父事務AD也不能commit。示例見2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
2、使用PROPAGATION_REQUIRES_NEW滿足需求2,但子事務(這時不應該稱之為子事務)BC是完全新的事務上下文,父事務(這時也不應該稱之為父事務)AD的成功與否完全不影響B(tài)C的提交,不能滿足需求1。
同時滿足上述兩條需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事務AD執(zhí)行到B點時,設置了savePoint(關鍵)。
當BC事務成功commit時,PROPAGATION_NESTED的行為與PROPAGATION_REQUIRED一樣。只有當事務AD在D點成功commit時,事務BC才真正commit,如果階段3執(zhí)行異常,導致事務AD rollback,事務BC也將一起rollback ,從而滿足了“聯(lián)合成功”。
當階段2執(zhí)行異常,導致BC事務rollback時,因為設置了savePoint,AD事務可以選擇與BC一起rollback或繼續(xù)階段3的執(zhí)行并保留階段1的執(zhí)行結(jié)果,從而滿足了“隔離失敗”。
當然,要明確一點,事務傳播策略的定義是在聲明或事務管理范圍內(nèi)的(首先是在EJB CMT規(guī)范中定義,Spring事務框架補充了PROPAGATION_NESTED),編程式的事務管理不存在事務傳播的問題。
四、PROPAGATION_NESTED的必要條件
上面大致講述了潛套事務的使用場景, 下面我們來看如何在 spring 中使用 PROPAGATION_NESTED, 首先來看 AbstractPlatformTransactionManager
// Create a TransactionStatus for an existing transaction.
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction,
boolean debugEnabled) throws TransactionException {
... 省略
if (definition.getPropagationBehavior() ==
TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException( "Transaction
manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name
[" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints.
// Never activates Spring synchronization.
DefaultTransactionStatus status = newTransactionStatus(definition,
transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
} else {
// Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
doBegin(transaction, definition);
boolean newSynchronization = (this.transactionSynchronization !=
SYNCHRONIZATION_NEVER);
return newTransactionStatus(definition, transaction, true,
newSynchronization, debugEnabled, null);
}
}
}
- 我們要設置 transactionManager 的 nestedTransactionAllowed 屬性為 true, 注意, 此屬性默認為 false!!! **
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法
// Create a savepoint and hold it for the transaction.
// @throws org.springframework.transaction.NestedTransactionNotSupportedException
// if the underlying transaction does not support savepoints
public void createAndHoldSavepoint() throws TransactionException {
setSavepoint(getSavepointManager().createSavepoint());
}
可以看到 Savepoint 是 SavepointManager.createSavepoint 實現(xiàn)的, 再看 SavepointManager 的層次結(jié)構(gòu), 發(fā)現(xiàn)其 Template 實現(xiàn)是JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager 中的 TransactonObject 都是它的子類 :
JdbcTransactionObjectSupport 告訴我們必須要滿足兩個條件才能 createSavepoint :
java.sql.Savepoint 必須存在, 即 jdk 版本要 1.4+ **
Connection.getMetaData().supportsSavepoints() 必須為 true, 即 jdbc drive 必須支持 JDBC 3.0 **
確保以上條件都滿足后, 你就可以嘗試使用 PROPAGATION_NESTED 了。