一,、前言:
事務(wù)的傳播行為(propagation)就是為了解決外層方法調(diào)用內(nèi)層事務(wù)方法的各個(gè)情況的。
接下來(lái)要說(shuō)的嵌套事務(wù)的使用是基于Spring聲明式事務(wù)管理中的注解@Transactional 方式的,。
二,、事務(wù)的傳播行為:
- @Transactional(propagation=Propagation.REQUIRED) :如果外層調(diào)用方法本身有事務(wù), 那么就加入到該事務(wù)中, 沒(méi)有的話新建一個(gè)(這是默認(rèn)的設(shè)置項(xiàng))
- @Transactional(propagation=Propagation.NOT_SUPPORTED) :以非事務(wù)方式運(yùn)行,如果外層調(diào)用方法存在事務(wù),,則把當(dāng)這個(gè)事務(wù)掛起,。
- @Transactional(propagation=Propagation.REQUIRES_NEW) :不管外層調(diào)用方法否存在事務(wù),都創(chuàng)建一個(gè)自己的事務(wù),外層調(diào)用方法的事務(wù)掛起,自己的執(zhí)行完畢,再執(zhí)行調(diào)用方事務(wù)
- @Transactional(propagation=Propagation.MANDATORY) :如果外層調(diào)用方法存在事務(wù),,則加入該事務(wù);如果外層調(diào)用方法沒(méi)有事務(wù),,則拋出異常
- @Transactional(propagation=Propagation.NEVER) :以非事務(wù)方式運(yùn)行,,如果外層調(diào)用方法存在事務(wù),則拋出異常,。
- @Transactional(propagation=Propagation.SUPPORTS) :如果外層調(diào)用方法存在事務(wù),,則加入該事務(wù);如果外層調(diào)用方法沒(méi)有事務(wù),,則以非事務(wù)的方式繼續(xù)運(yùn)行,。
- @Transactional(propagation=Propagation.NESTED) :如果外層調(diào)用方法存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來(lái)運(yùn)行,;如果外層調(diào)用方法沒(méi)有事務(wù),,則該取值等價(jià)于TransactionDefinition.PROPAGATION_REQUIRED
三、關(guān)于事務(wù)傳播行為:
傳播行為就是一個(gè)約定:“別的方法調(diào)用自己的時(shí)候會(huì)以怎樣的方式開(kāi)啟事務(wù)”,。
當(dāng)你給一個(gè)方法指定傳播行為的時(shí)候這時(shí)這個(gè)方法本身肯定是支持事務(wù)的方法,,然而調(diào)用你的方法卻不一定。
調(diào)用你的方法可能本身是個(gè)事務(wù)方法(service事務(wù)方法a調(diào)用service事務(wù)方法b,,也可能不是(controller調(diào)用service事務(wù)方法b / service非事務(wù)方法a調(diào)用service事務(wù)方法b)
然后就看傳播行為了,。
Spring默認(rèn)的是PROPAGATION_REQUIRED
事務(wù)的傳播行為我們一般都是用來(lái)解決嵌套事務(wù)的,所以我們一般使用最多的是上面加黑的三種:
四,、嵌套事務(wù):
嵌套事務(wù):就是事務(wù)方法A調(diào)用事務(wù)方法B,,外層調(diào)用方法和內(nèi)層被調(diào)用方法都是事務(wù)方法的情況。
一般我們不關(guān)心外層調(diào)用方法的事務(wù)傳播行為(用默認(rèn)的(不指定就行)),。而只關(guān)心內(nèi)層被調(diào)用方法的傳播行為,。
我們一般情況下,會(huì)有以下三種需求:
- 外層調(diào)用方法和內(nèi)層被調(diào)用方法,,有異常一起回滾,,沒(méi)問(wèn)題一起提交。(共用一個(gè)事務(wù))
- 內(nèi)層被調(diào)用方法回滾與否,,不會(huì)影響外層調(diào)用方法,。而外層調(diào)用方法出異常回滾,,也不會(huì)回滾內(nèi)層被調(diào)用方法(兩個(gè)獨(dú)立的事務(wù))
- 內(nèi)層被調(diào)用方法回滾與否,,不會(huì)影響外層調(diào)用方法。而外層調(diào)用方法出異?;貪L,,也會(huì)回滾內(nèi)層被調(diào)用方法(嵌套事務(wù))
這三種情況正好對(duì)應(yīng)三種最常用的傳播行為
1----->@Transactional(propagation=Propagation.REQUIRED) :
內(nèi)外層方法共用外層方法的事務(wù)
2----->@Transactional(propagation=Propagation.REQUIRES_NEW) :
當(dāng)執(zhí)行內(nèi)層被調(diào)用方法時(shí),外層方法的事務(wù)會(huì)掛起。兩個(gè)事務(wù)相互獨(dú)立,,不會(huì)相互影響,。
3----->@Transactional(propagation=Propagation.NESTED) :
理解Nested的關(guān)鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區(qū)別是,,PROPAGATION_REQUIRES_NEW另起一個(gè)事務(wù),,將會(huì)與他的父事務(wù)相互獨(dú)立, 而Nested的事務(wù)和他的父事務(wù)是相依的,,他的提交是要等和他的父事務(wù)一塊提交的,。也就是說(shuō),如果父事務(wù)最后回滾,,他也要回滾的,。
它看起來(lái)像這樣
class ServiceA {
public void methodA() {
// 數(shù)據(jù)庫(kù)操作等其他代碼
try {
// savepoint(虛擬的)
ServiceB.methodB(); // PROPAGATION_NESTED 級(jí)別
} catch (SomeException) {
// 執(zhí)行其他業(yè)務(wù), 如ServiceC.methodC();
}
// 其他操作代碼
}
}
也就是說(shuō)ServiceB.methodB失敗回滾,那么ServiceA.methodA也會(huì)回滾到savepoint點(diǎn)上,,ServiceA.methodA可以選擇另外一個(gè)分支,,比如 ServiceC.methodC,繼續(xù)執(zhí)行,,來(lái)嘗試完成自己的事務(wù),。
五、嵌套事務(wù)的使用:
關(guān)于使用我的代碼放到了我的github上了,。
1,、propagation=Propagation.REQUIRED的情況
內(nèi)層被調(diào)用事務(wù)方法
@Transactional(propagation=Propagation.REQUIRED)
public void testRequired(User inner) {
testDAO.insertUser(inner);
}
外層調(diào)用方法
@Override
//@Transactional(propagation=Propagation.REQUIRED) // 調(diào)用方法可以是事務(wù)方法也可以是非事務(wù)方法
public void testRequired(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testRequired(inner);
} catch(RuntimeException e){
log.error("內(nèi)層方法出現(xiàn)異常回滾",e);
}
}
拋異常是通過(guò),,插入的User對(duì)象的UserName重復(fù)控制的,,然后觀察數(shù)據(jù)庫(kù)就可以看到相應(yīng)的情況結(jié)果,。(你可以把代碼下載下來(lái)自己跑一下)
測(cè)試Main方法如下
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
OuterBean outerBean = (OuterBean) ctx.getBean("outerBeanImpl");
/**你能通過(guò)控制插入的數(shù)據(jù)的UserName重復(fù)產(chǎn)生異常*/
User outer = new User();
outer.setUsername("009");
outer.setName("zjl");
User inner = new User();
inner.setUsername("010");
inner.setName("zjl");
/** 選擇代碼進(jìn)行注釋?zhuān)瓿赡阆胍臏y(cè)試*/
outerBean.testRequired(outer, inner);
// outerBean.testRequiresNew(outer,inner);
//outerBean.testNested(outer,inner);
}
這種傳播行為能實(shí)現(xiàn):外層調(diào)用方法和內(nèi)層被調(diào)用方法,,有異常一起回滾,沒(méi)問(wèn)題一起提交
2,、propagation=Propagation.REQUIRES_NEW
內(nèi)層被調(diào)用事務(wù)方法
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void testRequiresNew(User inner) {
testDAO.insertUser(inner);
}
外層調(diào)用方法
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void testRequiresNew(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testRequiresNew(inner);
} catch(RuntimeException e){
log.error("內(nèi)層方法出現(xiàn)異?;貪L",e);
}
}
測(cè)試方法相同
這種傳播行為能實(shí)現(xiàn):內(nèi)層被調(diào)用方法回滾與否,不會(huì)影響外層調(diào)用方法,。而外層調(diào)用方法出異?;貪L,也不會(huì)回滾內(nèi)層被調(diào)用方法
3,、propagation=Propagation.NESTED
內(nèi)層被調(diào)用事務(wù)方法
@Override
@Transactional(propagation=Propagation.NESTED)
public void testNested(User inner) {
testDAO.insertUser(inner);
}
外層調(diào)用方法
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void testNested(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testNested(inner);
} catch(RuntimeException e){
log.error("內(nèi)層方法出現(xiàn)異?;貪L",e);
}
}
測(cè)試方法相同
這種傳播行為能實(shí)現(xiàn):內(nèi)層被調(diào)用方法回滾與否,不會(huì)影響外層調(diào)用方法,。而外層調(diào)用方法出異?;貪L,也會(huì)回滾內(nèi)層被調(diào)用方法
六、使用中的注意事項(xiàng):
1,、外層調(diào)用內(nèi)層方法是兩個(gè)事務(wù)的都要try catch 住調(diào)用內(nèi)層方法的代碼塊,。共用一個(gè)事務(wù)的不要try catch 住(要不就出下面2那個(gè)異常),。
因?yàn)镾pring聲明式事務(wù)處理是基于Aop的,,默認(rèn)情況下他會(huì)在方法拋出運(yùn)行時(shí)異常時(shí),攔截異?;貪L事務(wù),,然后會(huì)繼續(xù)向上拋出。 所以你要try catch 住要不外層調(diào)用方法會(huì)用相應(yīng)異常,,那傳播行為就沒(méi)有用了,。
// 就類(lèi)似這種
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void testNested(User outer, User inner) {
testDAO.insertUser(outer);
try{
innerBean.testNested(inner);
} catch(RuntimeException e){
log.error("內(nèi)層方法出現(xiàn)異常回滾",e);
}
}
2,、“Transaction rolled back because it has been marked as rollback-only ”異常的出現(xiàn)
出現(xiàn)場(chǎng)景:這種異常一般是在,,嵌套事務(wù)使用中,內(nèi)層事務(wù)使用默認(rèn)的事務(wù)傳播行為(Propagation.REQUIRED),,內(nèi)外共用一個(gè)事務(wù)時(shí),,外層方法把內(nèi)層方法try catch 住了,就會(huì)出現(xiàn),。
原因:內(nèi)層方法出異常了,,會(huì)向上拋異常,SpringAOP攔截到,,就會(huì)把事務(wù)標(biāo)志為rollback only,,就是準(zhǔn)備要回滾。
由于內(nèi)外方法共用一個(gè)事務(wù),,這時(shí)要是外層方法把這個(gè)異常捕獲了,,外層方法就繼續(xù)提交。但是事務(wù)標(biāo)記已經(jīng)置了,,那就會(huì)拋這個(gè)異常,。
3、同一的類(lèi)的事務(wù)方法是無(wú)法直接調(diào)用的,,如果 ServiceA.methodA調(diào)用 Service.methodB,會(huì)使被調(diào)用方法的事務(wù)失效
因?yàn)閟pring的事務(wù)是基于代理類(lèi)來(lái)實(shí)現(xiàn)的,。在controller里的service其實(shí)是代理對(duì)象,所以b方法的事務(wù)有效,。,,而在同一個(gè)類(lèi)中ServiceA.methodA調(diào)用 Service.methodB,你拿到的不是代理后的methodB,,所以事務(wù)會(huì)失效 解決方法很簡(jiǎn)單,,在methodA方法類(lèi)中獲取當(dāng)前對(duì)象的代理對(duì)象
ServiceA proxy =(ServiceA)AopContext.currentProxy();
proxy.b();
|