Spring事务传递性探讨

本篇主要讨论下面几点:

一: Spring 事务的传递性介绍

二: 第三方调用含有事务的Service抛异常方法探讨

 

一: Spring 事务的传递性介绍

    事务传播行为,所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

    TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

    TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。

    TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

    TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

    TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

    TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

    TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价TransactionDefinition.PROPAGATION_REQUIRED。

 

    这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

 

二: 第三方调用含有事务的Service抛异常方法探讨

    原始数据:

   

 

   

    假设术语如下:

    a)正常情况

      调用方为Conumer,  调用ServiceA的methodA, methodA调用ServiceA的 methodB和

ServiceA的methodC。

Java代码 

 收藏代码

  1. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  2. public void modifyCommodityInfo(Commodity commodity) {  
  3.     updateCommodityCatalog1(commodity);  
  4.     updateCommodityCatalog2(commodity);  
  5. }  
  6.   
  7. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  8. private void updateCommodityCatalog1(Commodity commodity) {  
  9.     commodity.setCatalog("catalog222222222");  
  10.     commodityDao.updateCommodity(commodity);  
  11. }  
  12.   
  13. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  14. private void updateCommodityCatalog2(Commodity commodity) {  
  15.     commodity.setName("name222");  
  16.     commodityDao.updateCommodity(commodity);  
  17. }  

 

   运行结果为:

    
   
 

 

 

 

    b)事务中一个方法跑出异常

      ServiceA的methodA 调用ServiceA的methodB和ServiceA的methodC,代码如下:

     

Java代码 

 收藏代码

  1. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  2. public void modifyCommodityInfo(Commodity commodity) {  
  3.     updateCommodityCatalog1(commodity);  
  4.     updateCommodityCatalog2(commodity);  
  5. }  
  6.   
  7. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  8. private void updateCommodityCatalog1(Commodity commodity) {  
  9.     commodity.setCatalog("catalog222222222");  
  10.     commodityDao.updateCommodity(commodity);  
  11. }  
  12.   
  13. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  14. private void updateCommodityCatalog2(Commodity commodity) {  
  15.     commodity.setName("name222");  
  16.     commodityDao.updateCommodity(commodity);  
  17.     throw new RuntimeException("222");  
  18. }  

   运行结果:


    

 


      c)捕获异常的情况

   ServiceA的methodA 调用ServiceA的methodB和ServiceA的methodC,代码如下:

Java代码 

 收藏代码

  1. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  2. public void modifyCommodityInfo(Commodity commodity) {  
  3.     updateCommodityCatalog1(commodity);  
  4.     try {  
  5.         updateCommodityCatalog2(commodity);  
  6.     } catch (Exception e) {  
  7.           
  8.     }  
  9.      
  10. }  
  11.   
  12. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  13. private void updateCommodityCatalog1(Commodity commodity) {  
  14.     commodity.setCatalog("catalog222222222");  
  15.     commodityDao.updateCommodity(commodity);  
  16. }  
  17.   
  18. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  19. private void updateCommodityCatalog2(Commodity commodity) {  
  20.     commodity.setName("name222");  
  21.     commodityDao.updateCommodity(commodity);  
  22.     throw new RuntimeException("222");  
  23. }  

 

    运行结果:

 

    

 

 

  d)跨服务事务

  ServiceA的methodA 调用 ServiceA的methodB和ServiceA的methodC和ServiceB的methodD,代码如下:

Java代码 

 收藏代码

  1. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  2.    public void modifyCommodityInfo(Commodity commodity) {  
  3.        updateCommodityCatalog1(commodity);  
  4.        updateCommodityCatalog2(commodity);  
  5.        updateCommodityCatalog3(commodity);  
  6.    }  
  7.   
  8.    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  9.    private void updateCommodityCatalog1(Commodity commodity) {  
  10.        commodity.setCatalog("catalog222222222");  
  11.        commodityDao.updateCommodity(commodity);  
  12.    }  
  13.   
  14.    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  15.    private void updateCommodityCatalog2(Commodity commodity) {  
  16.        commodity.setName("name222");  
  17.        commodityDao.updateCommodity(commodity);  
  18.    }  
  19.      
  20.    //另一个Service InnerService的方法  
  21.    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  22.    public void updateCommodityCatalog3(Commodity commodity) {  
  23.        commodity.setDescription("desc333");  
  24.        commodityDao.updateCommodity(commodity);  
  25.        throw new RuntimeException("333");  
  26.    }  
  27.      

    运行结果:

    

    假设MethodD抛出异常,则Consumer调用MethodA会发生怎样的情形? 

    发现Consumer调用MethodA的时候出现了运行时异常,UnexpectedRollbackException: “Transaction rolled back because it has been marked as rollback-only”。这是为什么呢?

 

    网上搜索了下,终于发现了一个合理的解释。当MethodA调用MethodD的时候,且两个方法都为required属性,根据事务传播级别,则methodA和methodD共享一个事务,当methodD抛出了异常,则共享事务回滚,但是被MethodA catch了,而MethodA又没有及时抛出异常,则MethodA正常执行到最后的时候,则会做提交事务的操作,但是事务已经被回滚了,所以才出现了上面的异常。

 

    既然这样,小弟就开始YY了一下,哪些情况会使调用方没有这个异常呢?经过与小伙伴们的思维碰撞,发现有一下几个方法。

   1) MethodA 不加事务,所以执行到最后就不会commit,SUPPORTS和NOT_SUPPORTED都可以实现这种功能。

   2) MethodD设置不共享事务,拥有自己单独的事务。验证发现,REQUIRES_NEW可以实现这种功能。

   

 

   e)

   代码如下:

   

Java代码 

 收藏代码

  1. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  2. public void modifyCommodityInfo(Commodity commodity) {  
  3.     updateCommodityCatalog1(commodity);  
  4.     updateCommodityCatalog2(commodity);  
  5.     try {  
  6.  commodityInnerService.updateCommodityCatalog3(commodity);  
  7.     } catch (Exception e) {  
  8.     }  
  9. }  
  10.   
  11. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  12. private void updateCommodityCatalog1(Commodity commodity) {  
  13.     commodity.setCatalog("catalog222222222");  
  14.     commodityDao.updateCommodity(commodity);  
  15. }  
  16.   
  17. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  18. private void updateCommodityCatalog2(Commodity commodity) {  
  19.     commodity.setName("name222");  
  20.     commodityDao.updateCommodity(commodity);  
  21. }  
  22.   
  23. //另一个Service InnerService的方法  
  24. @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)  
  25. public void updateCommodityCatalog3(Commodity commodity) {  
  26.     commodity.setDescription("desc333");  
  27.     commodityDao.updateCommodity(commodity);  
  28.     throw new RuntimeException("333");  
  29. }  

 

 结果如下:



 

 

 

 

   然后又YY了下,既然一个回滚的事务不能提交了,那么这个回滚的事务可以重复回滚吗?

   f)ServiceA的methodA 调用 ServiceA的methodB和ServiceA的methodC和ServiceB的methodD和ServiceB的methodE。代码如下:

   

Java代码 

 收藏代码

  1. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  2. public void modifyCommodityInfo(Commodity commodity) {  
  3.     updateCommodityCatalog1(commodity);  
  4.     updateCommodityCatalog2(commodity);  
  5.     try {  
  6.  ommodityInnerService.updateCommodityCatalog3(commodity);  
  7.     } catch (Exception e) {  
  8.     }  
  9.      try {  
  10.  commodityInnerService.updateCommodityCatalog4(commodity);  
  11.     } catch (Exception e) {  
  12.     }  
  13. }  
  14.   
  15. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  16. private void updateCommodityCatalog1(Commodity commodity) {  
  17.     commodity.setCatalog("catalog222222222");  
  18.     commodityDao.updateCommodity(commodity);  
  19. }  
  20.   
  21. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)  
  22. private void updateCommodityCatalog2(Commodity commodity) {  
  23.     commodity.setName("name222");  
  24.     commodityDao.updateCommodity(commodity);  
  25. }  
  26.   
  27. //另一个Service InnerService的方法  
  28. @Transactional(propagation = Propagation.REQUIRES, rollbackFor = Exception.class)  
  29. public void updateCommodityCatalog3(Commodity commodity) {  
  30.     commodity.setDescription("desc333");  
  31.     commodityDao.updateCommodity(commodity);  
  32.     throw new RuntimeException("333");  
  33. }  
  34.   
  35.  //另一个Service InnerService的方法  
  36. @Transactional(propagation = Propagation.REQUIRES, rollbackFor = Exception.class)  
  37. public void updateCommodityCatalog4(Commodity commodity) {  
  38.     commodity.setDescription("desc333");  
  39.     commodityDao.updateCommodity(commodity);  
  40.     throw new RuntimeException("333");  
  41. }  
  42.    

运行结果:

   发现还是只抛出了一个Transaction rolled back because it has been marked as rollback-only

所以猜测一个被共享的事务抛出多个异常的时候只是标记下rollback-only,而在方法结束的时候判断是执行事务还是回滚事务。

 

   总结:

    1) 单个ServiceA 内部调用不存在事务传播,相当于把methodB和methodC的代码嵌套到methodA的代码中,即使定义了也无法生效。

    2) 跨Service调用存在事务传播级别,需要考虑共享事务,还是新事务调用,即跨Service的调用是否需要需要回滚本Servcie的代码。

   

 

 参考:https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/

from: http://labreeze.iteye.com/blog/2277261

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值