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