管理兩種方式
spring支持編程式事務(wù)管理和聲明式事務(wù)管理兩種方式。
- 編程式事務(wù)管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager中燥。對(duì)于編程式事務(wù)管理寇甸,spring推薦使用TransactionTemplate。
- 聲明式事務(wù)管理建立在AOP之上的。其本質(zhì)是對(duì)方法前后進(jìn)行攔截拿霉,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個(gè)事務(wù)式塌,在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。
聲明式事務(wù)最大的優(yōu)點(diǎn)就是不需要通過編程的方式管理事務(wù)友浸,這樣就不需要在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼峰尝,只需在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或通過基于@Transactional注解的方式),便可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中收恢。
顯然聲明式事務(wù)管理要優(yōu)于編程式事務(wù)管理武学,這正是spring倡導(dǎo)的非侵入式的開發(fā)方式。聲明式事務(wù)管理使業(yè)務(wù)代碼不受污染伦意,一個(gè)普通的POJO對(duì)象火窒,只要加上注解就可以獲得完全的事務(wù)支持。和編程式事務(wù)相比驮肉,聲明式事務(wù)唯一不足地方是熏矿,后者的最細(xì)粒度只能作用到方法級(jí)別,無法做到像編程式事務(wù)那樣可以作用到代碼塊級(jí)別离钝。但是即便有這樣的需求票编,也存在很多變通的方法,比如卵渴,可以將需要進(jìn)行事務(wù)管理的代碼塊獨(dú)立為方法等等慧域。
聲明式事務(wù)管理也有兩種常用的方式,一種是基于tx和aop名字空間的xml配置文件浪读,另一種就是基于@Transactional注解昔榴。顯然基于注解的方式更簡單易用,更清爽碘橘。
聲明式事務(wù)的使用技巧
1互订、@Transactional可以作用于接口、接口方法痘拆、類仰禽、類方法上,當(dāng)作用到類時(shí)错负,該類下所有public方法都將具有該類型的事務(wù)屬性坟瓢,同時(shí),也可以在方法級(jí)別使用該注解來覆蓋類級(jí)別的定義犹撒。Spring的建議是在具體的實(shí)現(xiàn)類和類方法使用@Transactional注解折联,而不是使用在接口上。因?yàn)樽⒔獠荒芾^承识颊,不能被基于接口的代理類所識(shí)別诚镰,注解失效奕坟。
2、聲明式事務(wù)管理默認(rèn)只對(duì)非檢查型異常unchecked Exception進(jìn)行回滾清笨,也就是對(duì)RuntimeException異常以及它的子類進(jìn)行回滾操作月杉。
如果需要讓checked Exception也進(jìn)行回滾,需加上@Transactional(rollbackFor=Exception.class)抠艾、
如果需要讓unchecked Exception不進(jìn)行回滾苛萎,需加上@Transactional(notRollbackFor=Exception.class)
3、在Springboot使用聲明式事務(wù)需要在Application啟動(dòng)類加入@EnableTransactionManagement注解检号,相當(dāng)于Spring的自動(dòng)掃描
四腌歉、聲明式事務(wù)的常用配置
參 數(shù) 名 稱 | 功 能 描 述 |
---|---|
readOnly | 該屬性用于設(shè)置當(dāng)前事務(wù)是否為只讀事務(wù),設(shè)置為true表示只讀齐苛,false則表示可讀寫翘盖,默認(rèn)值為false。例如:@Transactional(readOnly=true) |
rollbackFor | 該屬性用于設(shè)置需要進(jìn)行回滾的異常類數(shù)組凹蜂,當(dāng)方法中拋出指定異常數(shù)組中的異常時(shí)馍驯,則進(jìn)行事務(wù)回滾。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)指定多個(gè)異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 該屬性用于設(shè)置需要進(jìn)行回滾的異常類名稱數(shù)組玛痊,當(dāng)方法中拋出指定異常名稱數(shù)組中的異常時(shí)汰瘫,則進(jìn)行事務(wù)回滾。例如:指定單一異常類名稱@Transactional(rollbackForClassName=”RuntimeException”)指定多個(gè)異常類名稱:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
noRollbackFor | 該屬性用于設(shè)置不需要進(jìn)行回滾的異常類數(shù)組卿啡,當(dāng)方法中拋出指定異常數(shù)組中的異常時(shí)吟吝,不進(jìn)行事務(wù)回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)指定多個(gè)異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 該屬性用于設(shè)置不需要進(jìn)行回滾的異常類名稱數(shù)組颈娜,當(dāng)方法中拋出指定異常名稱數(shù)組中的異常時(shí),不進(jìn)行事務(wù)回滾浙宜。例如:指定單一異常類名稱:@Transactional(noRollbackForClassName=”RuntimeException”)指定多個(gè)異常類名稱:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
propagation | 該屬性用于設(shè)置事務(wù)的傳播行為官辽。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 該屬性用于設(shè)置底層數(shù)據(jù)庫的事務(wù)隔離級(jí)別,事務(wù)隔離級(jí)別用于處理多事務(wù)并發(fā)的情況粟瞬,通常使用數(shù)據(jù)庫的默認(rèn)隔離級(jí)別即可同仆,基本不需要進(jìn)行設(shè)置 |
timeout | 該屬性用于設(shè)置事務(wù)的超時(shí)秒數(shù),默認(rèn)值為-1表示永不超時(shí) |
spring事務(wù)特性
spring所有的事務(wù)管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口其中TransactionDefinition接口定義以下特性:
事務(wù)隔離級(jí)別
隔離級(jí)別是指若干個(gè)并發(fā)的事務(wù)之間的隔離程度裙品。TransactionDefinition 接口中定義了五個(gè)表示隔離級(jí)別的常量:
- TransactionDefinition.ISOLATION_DEFAULT:這是默認(rèn)值俗批,表示使用底層數(shù)據(jù)庫的默認(rèn)隔離級(jí)別。對(duì)大部分?jǐn)?shù)據(jù)庫而言市怎,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED岁忘。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級(jí)別表示一個(gè)事務(wù)可以讀取另一個(gè)事務(wù)修改但還沒有提交的數(shù)據(jù)。該級(jí)別不能防止臟讀区匠,不可重復(fù)讀和幻讀干像,因此很少使用該隔離級(jí)別帅腌。比如PostgreSQL實(shí)際上并沒有此級(jí)別。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級(jí)別表示一個(gè)事務(wù)只能讀取另一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù)麻汰。該級(jí)別可以防止臟讀速客,這也是大多數(shù)情況下的推薦值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級(jí)別表示一個(gè)事務(wù)在整個(gè)過程中可以多次重復(fù)執(zhí)行某個(gè)查詢五鲫,并且每次返回的記錄都相同溺职。該級(jí)別可以防止臟讀和不可重復(fù)讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務(wù)依次逐個(gè)執(zhí)行位喂,這樣事務(wù)之間就完全不可能產(chǎn)生干擾浪耘,也就是說,該級(jí)別可以防止臟讀忆某、不可重復(fù)讀以及幻讀点待。但是這將嚴(yán)重影響程序的性能。通常情況下也不會(huì)用到該級(jí)別弃舒。
事務(wù)傳播行為
所謂事務(wù)的傳播行為是指癞埠,如果在開始當(dāng)前事務(wù)之前,一個(gè)事務(wù)上下文已經(jīng)存在聋呢,此時(shí)有若干選項(xiàng)可以指定一個(gè)事務(wù)性方法的執(zhí)行行為苗踪。在TransactionDefinition定義中包括了如下幾個(gè)表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果當(dāng)前存在事務(wù),則加入該事務(wù)削锰;如果當(dāng)前沒有事務(wù)通铲,則創(chuàng)建一個(gè)新的事務(wù)。這是默認(rèn)值器贩。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:創(chuàng)建一個(gè)新的事務(wù)颅夺,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起蛹稍。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù)吧黄,則加入該事務(wù);如果當(dāng)前沒有事務(wù)唆姐,則以非事務(wù)的方式繼續(xù)運(yùn)行拗慨。(注意這里,不要采坑)
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式運(yùn)行奉芦,如果當(dāng)前存在事務(wù)赵抢,則把當(dāng)前事務(wù)掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務(wù)方式運(yùn)行声功,如果當(dāng)前存在事務(wù)烦却,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當(dāng)前存在事務(wù)减噪,則加入該事務(wù)短绸;如果當(dāng)前沒有事務(wù)车吹,則拋出異常。
- TransactionDefinition.PROPAGATION_NESTED:如果當(dāng)前存在事務(wù)醋闭,則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行窄驹;如果當(dāng)前沒有事務(wù),則該取值等價(jià)于TransactionDefinition.PROPAGATION_REQUIRED证逻。
事務(wù)超時(shí)
所謂事務(wù)超時(shí)乐埠,就是指一個(gè)事務(wù)所允許執(zhí)行的最長時(shí)間,如果超過該時(shí)間限制但事務(wù)還沒有完成囚企,則自動(dòng)回滾事務(wù)丈咐。在 TransactionDefinition 中以int 的值來表示超時(shí)時(shí)間,其單位是秒龙宏。
默認(rèn)設(shè)置為底層事務(wù)系統(tǒng)的超時(shí)值棵逊,如果底層數(shù)據(jù)庫事務(wù)系統(tǒng)沒有設(shè)置超時(shí)值,那么就是none银酗,沒有超時(shí)限制辆影。
事務(wù)只讀屬性
只讀事務(wù)用于客戶代碼只讀但不修改數(shù)據(jù)的情形,只讀事務(wù)用于特定情景下的優(yōu)化黍特,比如使用Hibernate的時(shí)候蛙讥。
“只讀事務(wù)”并不是一個(gè)強(qiáng)制選項(xiàng),它只是一個(gè)“暗示”灭衷,提示數(shù)據(jù)庫驅(qū)動(dòng)程序和數(shù)據(jù)庫系統(tǒng)次慢,這個(gè)事務(wù)并不包含更改數(shù)據(jù)的操作,那么JDBC驅(qū)動(dòng)程序和數(shù)據(jù)庫就有可能根據(jù)這種情況對(duì)該事務(wù)進(jìn)行一些特定的優(yōu)化翔曲,比方說不安排相應(yīng)的數(shù)據(jù)庫鎖迫像,以減輕事務(wù)對(duì)數(shù)據(jù)庫的壓力,畢竟事務(wù)也是要消耗數(shù)據(jù)庫的資源的瞳遍。
但是你非要在“只讀事務(wù)”里面修改數(shù)據(jù)侵蒙,也并非不可以,只不過對(duì)于數(shù)據(jù)一致性的保護(hù)不像“讀寫事務(wù)”那樣保險(xiǎn)而已傅蹂。
因此,“只讀事務(wù)”僅僅是一個(gè)性能優(yōu)化的推薦配置而已算凿,并非強(qiáng)制你要這樣做不可份蝴。
spring事務(wù)回滾規(guī)則
指示spring事務(wù)管理器回滾一個(gè)事務(wù)的推薦方法是在當(dāng)前事務(wù)的上下文內(nèi)拋出異常。spring事務(wù)管理器會(huì)捕捉任何未處理的異常氓轰,然后依據(jù)規(guī)則決定是否回滾拋出異常的事務(wù)婚夫。默認(rèn)配置下,spring只有在拋出的異常為運(yùn)行時(shí)unchecked異常時(shí)才回滾該事務(wù)署鸡,也就是拋出的異常為RuntimeException的子類(Errors也會(huì)導(dǎo)致事務(wù)回滾)案糙,而拋出checked異常則不會(huì)導(dǎo)致事務(wù)回滾限嫌。可以明確的配置在拋出那些異常時(shí)回滾事務(wù)时捌,包括checked異常怒医。也可以明確定義那些異常拋出時(shí)不回滾事務(wù)。還可以編程性的通過setRollbackOnly()方法來指示一個(gè)事務(wù)必須回滾奢讨,在調(diào)用完setRollbackOnly()后你所能執(zhí)行的唯一操作就是回滾稚叹。
示例:基于注解的聲明式事務(wù)管理配置@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,名字為默認(rèn)值 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟事務(wù)控制的注解支持 -->
<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自動(dòng)參與到spring事務(wù)管理中拿诸,無需額外配置扒袖,只要org.mybatis.spring.SqlSessionFactoryBean引用的數(shù)據(jù)源與DataSourceTransactionManager引用的數(shù)據(jù)源一致即可,否則事務(wù)管理會(huì)不起作用亩码。
@Transactional注解
使用@Transactional時(shí)季率,可以指定如下屬性:
a、isolation:用于指定事務(wù)的隔離級(jí)別描沟。默認(rèn)為底層事務(wù)的隔離級(jí)別飒泻。
b、noRollbackFor:指定遇到指定異常時(shí)強(qiáng)制不回滾事務(wù)啊掏。
c蠢络、noRollbackForClassName:指定遇到指定多個(gè)異常時(shí)強(qiáng)制不回滾事務(wù)。該屬性可以指定多個(gè)異常類名迟蜜。
d刹孔、propagation:指定事務(wù)的傳播屬性。
e娜睛、readOnly:指定事務(wù)是否只讀髓霞。
f、rollbackFor:指定遇到指定異常時(shí)強(qiáng)制回滾事務(wù)畦戒。
g方库、rollbackForClassName:指定遇到指定多個(gè)異常時(shí)強(qiáng)制回滾事務(wù)。該屬性可以指定多個(gè)異常類名。
h抵乓、timeout:指定事務(wù)的超時(shí)時(shí)長摆寄。**
事務(wù)可回滾要注意點(diǎn)*
1、被注解的必須是public
@Transactional 可以作用于接口邀层、接口方法、類以及類方法上遂庄。當(dāng)作用于類上時(shí)寥院,該類的所有 public方法將都具有該類型的事務(wù)屬性,同時(shí)涛目,我們也可以在方法級(jí)別使用該標(biāo)注來覆蓋類級(jí)別的定義秸谢。
雖然 @Transactional 注解可以作用于接口凛澎、接口方法、類以及類方法上估蹄,但是 Spring 建議不要在接口或者接口方法上使用該注解塑煎,因?yàn)檫@只有在使用基于接口的代理時(shí)它才會(huì)生效。另外元媚, @Transactional 注解應(yīng)該只被應(yīng)用到 public 方法上轧叽,這是由 Spring AOP 的本質(zhì)決定的。如果你在 protected刊棕、private 或者默認(rèn)可見性的方法上使用 @Transactional 注解炭晒,這將被忽略,也不會(huì)拋出任何異常甥角。
poxy-target-class="true"表示使用CGLib動(dòng)態(tài)代理技術(shù)織入增強(qiáng)网严。不過即使proxy-target-class設(shè)置為false,如果目標(biāo)類沒有聲明接口嗤无,則spring將自動(dòng)使用CGLib動(dòng)態(tài)代理震束。</pre>
2、rollbackFor和noRollbackFor
需我們指定方式來讓事務(wù)回滾 :
要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常})
如果讓unchecked例外不回滾:@Transactional(notRollbackFor=RunTimeException.class)
(關(guān)于rollbackFor配置的經(jīng)歷)
當(dāng)?shù)腡ransactional中配置rollbackFor = Exception.class時(shí)当犯,拋出RuntimeException時(shí)是會(huì)回滾的垢村。但是如果是Unchecked Exceptions則不會(huì)回滾。
于是查看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都會(huì)回滾拓诸,不僅是RuntimeException侵佃,也包括Error。
3奠支、來自外部的方法調(diào)用才會(huì)被AOP代理捕獲
默認(rèn)情況下馋辈,只有來自外部的方法調(diào)用才會(huì)被AOP代理捕獲,也就是倍谜,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會(huì)引起事務(wù)行為迈螟,即使被調(diào)用方法使用@Transactional注解進(jìn)行修飾。(我碰到后尔崔,只有重構(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");
}
示例三:當(dāng)作用于類上時(shí)蚕脏,該類所有 public 方法將都具有該類型的事務(wù)屬性。
@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,以非事務(wù)方式運(yùn)行尺碰,不會(huì)存入數(shù)據(jù)庫
dao.insert(test);
}
事物注解方式: @Transactional
當(dāng)標(biāo)于類前時(shí), 標(biāo)示類中所有方法都進(jìn)行事物處理 , 例子:
@Transactional
public class TestServiceBean implements TestService {
}
當(dāng)類中某些方法不需要事物時(shí):
@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)
如果有事務(wù), 那么加入事務(wù), 沒有的話新建一個(gè)(默認(rèn)情況下)@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不為這個(gè)方法開啟事務(wù)@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事務(wù),都創(chuàng)建一個(gè)新的事務(wù),原來的掛起,新的執(zhí)行完畢,繼續(xù)執(zhí)行老的事務(wù)@Transactional(propagation=Propagation.MANDATORY)
必須在一個(gè)已有的事務(wù)中執(zhí)行,否則拋出異常@Transactional(propagation=Propagation.NEVER)
必須在一個(gè)沒有的事務(wù)中執(zhí)行,否則拋出異常(與Propagation.MANDATORY相反)@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean調(diào)用這個(gè)方法,在其他bean中聲明事務(wù),那就用事務(wù).如果其他bean沒有聲明事務(wù),那就不用事務(wù).
事物超時(shí)設(shè)置:
@Transactional(timeout=30) //默認(rèn)是30秒
事務(wù)隔離級(jí)別:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
讀取未提交數(shù)據(jù)(會(huì)出現(xiàn)臟讀, 不可重復(fù)讀) 基本不使用@Transactional(isolation = Isolation.READ_COMMITTED)
讀取已提交數(shù)據(jù)(會(huì)出現(xiàn)不可重復(fù)讀和幻讀)@Transactional(isolation = Isolation.REPEATABLE_READ)
可重復(fù)讀(會(huì)出現(xiàn)幻讀)@Transactional(isolation = Isolation.SERIALIZABLE)
串行化
MYSQL:默認(rèn)為REPEATABLE_READ級(jí)別
SQLSERVER:默認(rèn)為READ_COMMITTED
臟讀 : 一個(gè)事務(wù)讀取到另一事務(wù)未提交的更新數(shù)據(jù)挣棕。
不可重復(fù)讀 : 在同一事務(wù)中, 多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同。后續(xù)讀取可以讀到另一事務(wù)已提交的更新數(shù)據(jù)亲桥。
可重復(fù)讀:在同一事務(wù)中多次讀取數(shù)據(jù)時(shí), 能夠保證所讀數(shù)據(jù)一樣, 也就是后續(xù)讀取不能讀到另一事務(wù)已提交的更新數(shù)據(jù)洛心。
幻讀 : 一個(gè)事務(wù)讀到另一個(gè)事務(wù)已提交的insert數(shù)據(jù)。
@Transactional注解中常用參數(shù)說明
參數(shù)名稱 | 功能描述 |
---|---|
readOnly | 該屬性用于設(shè)置當(dāng)前事務(wù)是否為只讀事務(wù)题篷,設(shè)置為true表示只讀词身,false則表示可讀寫,默認(rèn)值為false番枚。例如:@Transactional(readOnly=true) |
rollbackFor | 該屬性用于設(shè)置需要進(jìn)行回滾的異常類數(shù)組法严,當(dāng)方法中拋出指定異常數(shù)組中的異常時(shí),則進(jìn)行事務(wù)回滾葫笼。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)深啤;指定多個(gè)異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 該屬性用于設(shè)置需要進(jìn)行回滾的異常類名稱數(shù)組,當(dāng)方法中拋出指定異常名稱數(shù)組中的異常時(shí)路星,則進(jìn)行事務(wù)回滾溯街。例如:指定單一異常類名稱@Transactional(rollbackForClassName="RuntimeException");指定多個(gè)異常類名稱:@Transactional(rollbackForClassName{"RuntimeException","Exception"}) |
noRollbackFor | 該屬性用于設(shè)置不需要進(jìn)行回滾的異常類數(shù)組奥额,當(dāng)方法中拋出指定異常數(shù)組中的異常時(shí)苫幢,不進(jìn)行事務(wù)回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)垫挨;指定多個(gè)異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 該屬性用于設(shè)置不需要進(jìn)行回滾的異常類名稱數(shù)組韩肝,當(dāng)方法中拋出指定異常名稱數(shù)組中的異常時(shí),不進(jìn)行事務(wù)回滾九榔。例如:指定單一異常類名稱@Transactional(noRollbackForClassName="RuntimeException") |
指定多個(gè)異常類名稱:
@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
參數(shù)名稱 | 功能描述 |
---|---|
propagation | 該屬性用于設(shè)置事務(wù)的傳播行為哀峻,具體取值可參考表6-7。例@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 該屬性用于設(shè)置底層數(shù)據(jù)庫的事務(wù)隔離級(jí)別哲泊,事務(wù)隔離級(jí)別用于處理多事務(wù)并發(fā)的情況剩蟀,通常使用數(shù)據(jù)庫的默認(rèn)隔離級(jí)別即可,基本不需要進(jìn)行設(shè)置 |
timeout | 該屬性用于設(shè)置事務(wù)的超時(shí)秒數(shù)切威,默認(rèn)值為-1表示永不超時(shí) |
注意的幾點(diǎn):
1育特、@Transactional 只能被應(yīng)用到public方法上, 對(duì)于其它非public的方法,如果標(biāo)記了@Transactional也不會(huì)報(bào)錯(cuò),但方法沒有事務(wù)功能.
2、用 spring 事務(wù)管理器,由spring來負(fù)責(zé)數(shù)據(jù)庫的打開,提交,回滾.默認(rèn)遇到運(yùn)行期例外(throw new RuntimeException("注釋");)會(huì)回滾,即遇到不受檢查(unchecked)的例外時(shí)回滾缰冤;而遇到需要捕獲的例外(throw new Exception("注釋");)不會(huì)回滾,即遇到受檢查的例外(就是非運(yùn)行時(shí)拋出的異常犬缨,編譯器會(huì)檢查到的異常叫受檢查例外或說受檢查異常)時(shí),需我們指定方式來讓事務(wù)回滾要想所有異常都回滾棉浸。
要加上 @Transactional( rollbackFor={Exception.class,其它異常}) 怀薛。如果讓unchecked例外不回滾:@Transactional(notRollbackFor=RunTimeException.class)如下:
@Transactional(rollbackFor=Exception.class)
//指定回滾,遇到異常Exception時(shí)回滾
public void methodName() {
throw new Exception("注釋");
}
@Transactional(noRollbackFor=Exception.class)
//指定不回滾,遇到運(yùn)行期例外(throw new RuntimeException("注釋");)會(huì)回滾
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("注釋");
}
3、@Transactional 注解應(yīng)該只被應(yīng)用到 public 可見度的方法上迷郑。 如果你在 protected枝恋、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會(huì)報(bào)錯(cuò)嗡害, 但是這個(gè)被注解的方法將不會(huì)展示已配置的事務(wù)設(shè)置焚碌。
4、@Transactional 注解可以被應(yīng)用于接口定義和接口方法就漾、類定義和類的 public 方法上呐能。然而,請(qǐng)注意僅僅 @Transactional 注解的出現(xiàn)不足于開啟事務(wù)行為抑堡,它僅僅 是一種元數(shù)據(jù)摆出,能夠被可以識(shí)別 @Transactional 注解和上述的配置適當(dāng)?shù)木哂惺聞?wù)行為的beans所使用。上面的例子中首妖,其實(shí)正是 元素的出現(xiàn) 開啟 了事務(wù)行為偎漫。
5、Spring團(tuán)隊(duì)的建議是你在具體的類(或類的方法)上使用 @Transactional 注解有缆,而不要使用在類所要實(shí)現(xiàn)的任何接口上象踊。你當(dāng)然可以在接口上使用 @Transactional 注解,但是這將只能當(dāng)你設(shè)置了基于接口的代理時(shí)它才生效棚壁。因?yàn)樽⒔馐遣荒芾^承的杯矩,這就意味著如果你正在使用基于類的代理時(shí),那么事務(wù)的設(shè)置將不能被基于類的代理所識(shí)別袖外,而且對(duì)象也將不會(huì)被事務(wù)代理所包裝(將被確認(rèn)為嚴(yán)重的)史隆。因此,請(qǐng)接受Spring團(tuán)隊(duì)的建議并且在具體的類上使用 @Transactional 注解曼验。
嵌套事務(wù)示例
-
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;
/** * 嵌套事務(wù)測試 */
@PostMapping(value = "/test1")
public String methodA() throws Exception {
serviceA.methodA();
return "ok";
}
}
結(jié)果:
看數(shù)據(jù)庫表記錄:
這種情況下, 因?yàn)?ServiceB#methodB 的事務(wù)屬性為 PROPAGATION_REQUIRES_NEW泌射,ServiceB是一個(gè)獨(dú)立的事務(wù),與外層事務(wù)沒有任何關(guān)系鬓照。如果ServiceB執(zhí)行失斎劭帷(上面示例中讓ServiceB的id為已經(jīng)存在的值),ServiceA的調(diào)用出會(huì)拋出異常豺裆,導(dǎo)致ServiceA的事務(wù)回滾拒秘。
并且, 在 ServiceB#methodB 執(zhí)行時(shí) ServiceA#methodA 的事務(wù)已經(jīng)掛起了 (關(guān)于事務(wù)掛起的內(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+無事務(wù)注解
//ServiceA //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
//ServiceB //...
//沒有加事務(wù)注解
public void methodB(String id) {
//..
}
}
--“1”可插入凛俱,“2”可插入紊馏,“3”不可插入:
結(jié)果是“1”,“2”蒲犬,“3”都不能插入朱监,“1”,“2”被回滾原叮。
2.4赫编、內(nèi)層事務(wù)被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)層事務(wù)出錯(cuò)啦。");
}
}
//ServiceB //...
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...
}
--“1”可插入奋隶,“2”不可插入擂送,“3”可插入:
結(jié)果是“1”,“2”唯欣,“3”都不能插入嘹吨,“1”被回滾。
事務(wù)設(shè)置為Propagation.REQUIRED時(shí)黍聂,如果內(nèi)層方法拋出Exception躺苦,外層方法中捕獲Exception但是并沒有繼續(xù)向外拋出,最后出現(xiàn)“Transaction rolled back because it has been marked as rollback-only
”的錯(cuò)誤产还。外層的方法也將會(huì)回滾匹厘。
其原因是:內(nèi)層方法拋異常返回時(shí),transacation被設(shè)置為rollback-only了脐区,但是外層方法將異常消化掉愈诚,沒有繼續(xù)向外拋,那么外層方法正常結(jié)束時(shí),transaction會(huì)執(zhí)行commit操作炕柔,但是transaction已經(jīng)被設(shè)置為rollback-only了酌泰。所以,出現(xiàn)“Transaction rolled back because it has been marked as rollback-only”錯(cuò)誤匕累。
-
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)層事務(wù)出錯(cuò)啦陵刹。");
}
}
//ServiceB //...
@Transactional(propagation = Propagation.NESTED, readOnly = false)
public void methodB(String id) {
//...
}
--“1”可插入,“2”不可插入欢嘿,“3”可插入:
結(jié)果是“1”衰琐,“3"記錄插入成功,“2”記錄插入失敗炼蹦。
說明:
當(dāng)內(nèi)層配置成 PROPAGATION_NESTED, 此時(shí)兩者之間又將如何協(xié)作呢? 從 Juergen Hoeller 的原話中我們可以找到答案, ServiceB#methodB 如果 rollback, 那么內(nèi)部事務(wù)(即 ServiceB#methodB) 將回滾到它執(zhí)行前的 SavePoint(注意, 這是本文中第一次提到它, 潛套事務(wù)中最核心的概念), 而外部事務(wù)(即 ServiceA#methodA) 可以有以下兩種處理方式:
- 內(nèi)層失敗羡宙,外層調(diào)用其它分支。
ServiceA {
// 事務(wù)屬性配置為 PROPAGATION_REQUIRED
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 執(zhí)行其他業(yè)務(wù), 如 ServiceC.methodC();
}
}
這種方式也是潛套事務(wù)最有價(jià)值的地方, 它起到了分支執(zhí)行的效果, 如果ServiceB.methodB 失敗, 那么執(zhí)行 ServiceC.methodC(), 而 ServiceB.methodB 已經(jīng)回滾到它執(zhí)行之前的 SavePoint, 不會(huì)產(chǎn)生臟數(shù)據(jù)(相當(dāng)于此方法從未執(zhí)行過), 該特性可用在某些特殊的業(yè)務(wù)中, PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點(diǎn)掐隐。
- 代碼不做任何修改, 那么如果內(nèi)部事務(wù)(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滾到它執(zhí)行之前的 SavePoint(在任何情況下都會(huì)如此), 外部事務(wù)(即 ServiceA#methodA) 將根據(jù)具體的配置決定自己是 commit 還是 rollback狗热。
三、嵌套事務(wù)總結(jié)
使用嵌套事務(wù)的場景有兩點(diǎn)需求:
- 需要事務(wù)BC與事務(wù)AD一起commit虑省,即:作為事務(wù)AD的子事務(wù)匿刮,事務(wù)BC只有在事務(wù)AD成功commit時(shí)(階段3成功)才commit。這個(gè)需求簡單稱之為“聯(lián)合成功”慷妙。這一點(diǎn)PROPAGATION_NESTED和PROPAGATION_REQUIRED可以做到僻焚。
- 需要事務(wù)BC的rollback不(無條件的)影響事務(wù)AD的commit。這個(gè)需求簡單稱之為“隔離失敗”膝擂。這一點(diǎn)PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW可以做到虑啤。
分解下,可知PROPAGATION_NESTED的特殊性有:
1架馋、使用PROPAGATION_REQUIRED滿足需求1狞山,但子事務(wù)BC的rollback會(huì)無條件地使父事務(wù)AD也rollback,不能滿足需求2叉寂。即使對(duì)子事務(wù)進(jìn)行了try-catch萍启,父事務(wù)AD也不能commit。示例見2.4.1屏鳍、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
2勘纯、使用PROPAGATION_REQUIRES_NEW滿足需求2,但子事務(wù)(這時(shí)不應(yīng)該稱之為子事務(wù))BC是完全新的事務(wù)上下文钓瞭,父事務(wù)(這時(shí)也不應(yīng)該稱之為父事務(wù))AD的成功與否完全不影響B(tài)C的提交驳遵,不能滿足需求1。
同時(shí)滿足上述兩條需求就要用到PROPAGATION_NESTED了山涡。PROPAGATION_NESTED在事務(wù)AD執(zhí)行到B點(diǎn)時(shí)堤结,設(shè)置了savePoint(關(guān)鍵)唆迁。
當(dāng)BC事務(wù)成功commit時(shí),PROPAGATION_NESTED的行為與PROPAGATION_REQUIRED一樣竞穷。只有當(dāng)事務(wù)AD在D點(diǎn)成功commit時(shí)唐责,事務(wù)BC才真正commit,如果階段3執(zhí)行異常瘾带,導(dǎo)致事務(wù)AD rollback鼠哥,事務(wù)BC也將一起rollback ,從而滿足了“聯(lián)合成功”看政。
當(dāng)階段2執(zhí)行異常肴盏,導(dǎo)致BC事務(wù)rollback時(shí),因?yàn)樵O(shè)置了savePoint帽衙,AD事務(wù)可以選擇與BC一起rollback或繼續(xù)階段3的執(zhí)行并保留階段1的執(zhí)行結(jié)果,從而滿足了“隔離失敗”贞绵。
當(dāng)然厉萝,要明確一點(diǎn),事務(wù)傳播策略的定義是在聲明或事務(wù)管理范圍內(nèi)的(首先是在EJB CMT規(guī)范中定義榨崩,Spring事務(wù)框架補(bǔ)充了PROPAGATION_NESTED)谴垫,編程式的事務(wù)管理不存在事務(wù)傳播的問題。
四母蛛、PROPAGATION_NESTED的必要條件
上面大致講述了潛套事務(wù)的使用場景, 下面我們來看如何在 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);
}
}
}
- 我們要設(shè)置 transactionManager 的 nestedTransactionAllowed 屬性為 true, 注意, 此屬性默認(rèn)為 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 實(shí)現(xiàn)的, 再看 SavepointManager 的層次結(jié)構(gòu), 發(fā)現(xiàn)其 Template 實(shí)現(xiàn)是JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager 中的 TransactonObject 都是它的子類 :
JdbcTransactionObjectSupport 告訴我們必須要滿足兩個(gè)條件才能 createSavepoint :
java.sql.Savepoint 必須存在, 即 jdk 版本要 1.4+ **
Connection.getMetaData().supportsSavepoints() 必須為 true, 即 jdbc drive 必須支持 JDBC 3.0 **
確保以上條件都滿足后, 你就可以嘗試使用 PROPAGATION_NESTED 了翩剪。