博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring声明式事务为何不回滚
阅读量:4958 次
发布时间:2019-06-12

本文共 4192 字,大约阅读时间需要 13 分钟。

Spring声明式事务为何不回滚

2016.12.29 22:35* 字数 1670 阅读 7310评论 35

疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。下面总结一下经验教训:

Spring事务的管理操作方法

下面先总结一下Spring的事务管理方式,spring支持两种事务管理的操作方式,编程式的和声明式的(xml或者注解)。

  • 编程式的事务管理
  • 实际应用中很少使用
  • 通过使用TransactionTemplate 手动管理事务
  • 声明式的事务管理
  • 开发中推荐使用(代码侵入最少)
  • Spring的声明式事务是通过AOP实现的

主要掌握声明式的事务管理。

spring事务不回滚的两个原因

总结一下导致事务不回滚的两个原因,一是Service类内部方法调用,二是try...catch异常。

1. Service类内部方法调用

大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。示例代码如下:

@Servicepublic class RabbitServiceImpl implements RabbitService { @Autowired private RabbitDao rabbitDao; @Autowired private TortoiseDao tortoiseDao; @Override public Rabbit methodA(String name){ return methodB(name); } @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED) public boolean methodB(String name){ rabbitDao.insertRabbit(name); tortoiseDao.insertTortoise(name); return true; } }

单元测试代码如下:

public class RabbitServiceImplTest { @Autowired private RabbitService rabbitService; // 事务未开启 @Test public void testA(){ rabbitService.methodA("rabbit"); } // 事务开启 @Test public void testB(){ rabbitService.methodB("rabbit"); } }

从上一节中可以看到,声明式事务是通通过AOP动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。

对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器TransactionInterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图:

事务的调用原理

总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。

2. try...catch异常

在一段业务逻辑中对数据库异常进行了处理,使用了try...catch子句捕获异常并throw了一个自定义异常,这种情况导致了事务未回滚,示例代码如下:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)public boolean methodB(String name) throws BizException { try { rabbitDao.insertRabbit(name); tortoiseDao.insertTortoise(name); } catch (Exception e) { throw new BizException(ReturnCode.EXCEPTION.code, ReturnCode.EXCEPTION.msg); } return true; }

BizException的定义如下:

public class BizException extends Exception { // 自定义异常 }

上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是开始查询原因,翻看了,找到了答案。下面是翻译自Spring官网。

17.5.3 声明式事务的回滚

上一节中介绍了如何设置开启Spring事务,一般在你的应用的Service层代码中设置,这一节将介绍在简单流行的声明式事务中如何控制事务回滚。

在Spring FrameWork 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,Spring FrameWork 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。

  • 在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常时RuntimeException或者是其子类,这样事务才会回滚(默认情况下Error也会导致事务回滚)。在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。

注:Unchecked Exception包括Error与RuntimeException. RuntimeException的所有子类也都属于此类。另一类就是checked Exception。

  • 你可以精确的配置异常类型,指定此异常类事务回滚,包括 checked 异常。下面的xml代码片段展示了如何配置checked异常引起事务回滚,应用自定义异常类型:

与其有同等作用的注解形式如下:

@Transactional(rollbackForClassName={"NoProductInStockException"})或者@Transactional(rollbackFor={ NoProductInStockException.class})
  • 在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 InstrumentNotFoundException 异常时,Spring FrameWork 的事务框架同样会提交事务,而不回滚。

与其有同样作用的注解形式如下:

@Transactional(noRollbackForClassName={"InstrumentNotFoundException"})或者@Transactional(noRollbackFor={ InstrumentNotFoundException.class})
  • 还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当Spring FrameWork 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 InstrumentNotFoundException 之外的任何异常都会导致事务回滚。
  • 你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和Spring FrameWork 的事务框架代码紧密的绑定在一起,示例代码如下:
public void resolvePosition() { try { // some business logic... } catch (NoProductInStockException ex) { // trigger rollback programmatically TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }

如果可能的话,强烈推荐您使用声明式事务方式回滚事务,对于编程式事务,如果你强烈需要它,也是可以使用的,but its usage flies in the face of achieving a clean POJO-based architecture.(没懂...)

看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 RuntimeException。我的解决办法是,在注解@Transactional中添加 rollbackFor={BizException.class}。可能你会问我为什么不将自定义异常修改为继承RuntimeException,因为我需要BizException是一个checked 异常。

结束语:终于将spring事务中的异常回滚机制搞明白啦,欢迎读者在评论区添加其他导致spring事务不回滚的原因。

转载于:https://www.cnblogs.com/libin6505/p/9811261.html

你可能感兴趣的文章
SharePoint BDC(Business Data Connectivity)服务-PowerShell
查看>>
在Lumia 950 XL上运行Windows 10 ARM64,是种什么体验?
查看>>
源 ppa
查看>>
写给五年前的自己(软件测试工程师总结)(未更新完)
查看>>
在Windows上远程运行Linux程序
查看>>
mac xcworkspace xcodebuild
查看>>
把纯真IP数据库中的记录导入Mysql数据库的PHP脚本
查看>>
ActiveMQ:JMS开源框架入门介绍
查看>>
Mac下的裁剪快捷键
查看>>
通过51degrees.mobi 2.1.15.1 检测UserAgent判断是否为手机,并获取手机硬件型号
查看>>
Windows Server 2012及以上安装IIS的步骤
查看>>
ios swift 计算文件夹大小以及清除缓存文件
查看>>
vCenter 6.5安装
查看>>
关于linux下jdk的安装与环境配置(来自朋友Janie)
查看>>
I18n国际化
查看>>
由init、loadView、viewDidLoad、viewDidUnload、dealloc的关系说起(转)
查看>>
Jquery获取select,dropdownlist,checkbox下拉列表框的值
查看>>
webserver ZooKeeper Cluster
查看>>
POJ 2594 Treasure Exploration(Floyd+最小路径覆盖)
查看>>
WPF 简易进度条效果
查看>>