事務(wù)的傳播行為(propagation)就是為了解決外層方法調(diào)用內(nèi)層事務(wù)方法的各個(gè)情況的。
接下來(lái)要說(shuō)的嵌套事務(wù)的使用是基于Spring聲明式事務(wù)管理中的注解@Transactional 方式的。
傳播行為就是一個(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ù)方法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ì)有以下三種需求:
這三種情況正好對(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ù)。
關(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)異?;貪L",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)行注釋,完成你想要的測(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)用方法
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)有用了。
// 就類似這種 @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); } }
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、同一的類的事務(wù)方法是無(wú)法直接調(diào)用的,如果 ServiceA.methodA調(diào)用 Service.methodB,會(huì)使被調(diào)用方法的事務(wù)失效
因?yàn)閟pring的事務(wù)是基于代理類來(lái)實(shí)現(xiàn)的。在controller里的service其實(shí)是代理對(duì)象,所以b方法的事務(wù)有效。,而在同一個(gè)類中ServiceA.methodA調(diào)用 Service.methodB,你拿到的不是代理后的methodB,所以事務(wù)會(huì)失效
解決方法很簡(jiǎn)單,在methodA方法類中獲取當(dāng)前對(duì)象的代理對(duì)象ServiceA proxy =(ServiceA)AopContext.currentProxy();proxy.b();
聯(lián)系客服