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