完美解决Spring声明式事务不回滚的问题
作者:jingxian 发布时间:2023-07-12 14:38:50
疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。
下面总结一下经验教训:
Spring事务的管理操作方法
编程式的事务管理
实际应用中很少使用
通过使用TransactionTemplate 手动管理事务
声明式的事务管理
开发中推荐使用(代码侵入最少)
Spring的声明式事务是通过AOP实现的
主要掌握声明式的事务管理。
spring事务不回滚的两个原因
总结一下导致事务不回滚的两个原因,一是Service类内部方法调用,二是try...catch异常。
1. Service类内部方法调用
大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。示例代码如下:
@Service
public 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)
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)
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的官方文档,找到了答案。下面是翻译自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异常引起事务回滚,应用自定义异常类型:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="Exception"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
与其有同等作用的注解形式如下:
@Transactional(rollbackForClassName={"Exception"})
或者
@Transactional(rollbackFor={Exception.class})
在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 InstrumentNotFoundException 异常时,Spring FrameWork 的事务框架同样会提交事务,而不回滚。
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
与其有同样作用的注解形式如下:
@Transactional(noRollbackForClassName={"InstrumentNotFoundException"})
或者
@Transactional(noRollbackFor={InstrumentNotFoundException.class})
还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当Spring FrameWork 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 InstrumentNotFoundException 之外的任何异常都会导致事务回滚。
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和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核心是 IOC 和 AOP 。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和
- SlidingDrawer效果想必大家也见到过,它就是1.5模拟器上进入应用程序列表的效果。下面是截图一、简介 SlidingDr
- 什么是Etcd?etcd是一个强大的一致性的分布式键值存储,它提供了一种可靠的方式来存储需要由分布式系统或机器群访问的数据。它优雅地处理网络
- Mybatis动态排序 #{} ${}问题在写Mybatis动态排序是遇到一个问题,开始,我是这样写的<if test="o
- 创建类的对象 = 类的实例化 = 实例化类类和对象的使用(面向对象思想落地的实现):1.创建类,设计类的成员2.创建类的对象3.通过“对象.
- 注解从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置。那充满争议的类型注解究竟是什
- JWT可以理解为一个加密的字符串,里面由三部分组成:头部(Header)、负载(Payload)、签名(signature)由base64加
- 故事背景故事发生在几个星期前,自动化平台代码开放给整个测试团队以后,越来越多的同事开始探索平台代码。为了保障自动化测试相关的数据和沉淀能不被
- 概述要想了解会话跟踪技术,我想我们要先了解一下会话是什么,以及会话跟踪技术存在的意义。首先我们要说的是:会话。会话 :见名知意,在现实中我们
- 本文实例讲述了Java Swing中JTable渲染器与编辑器用法。分享给大家供大家参考,具体如下:JTable的内容、外观、事件响应在很大
- 展示图: 对接的完整流程如下首先是配置gzh.appid=公众号appidwxPay.mchId=商户号wxPay.key=支付密
- 1. 下载myeclipse版本对应的STS插件下载地址:https://spring.io/tools/sts/all2.在myeclip
- java 图片转base64字符串、base64字符串转图片,具体内容如下1. 图片转base64字符串: /** * base
- 前言:在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程
- 前言最近在做分块上传的业务,使用到了Redis来维护上传过程中的分块编号。每上传完成一个分块就获取一下文件的分块集合,加入新上传的编号,手动
- Java的List在删除元素时,一般会用list.remove(o)/remove(i)方法。在使用时,容易触碰陷阱,得到意想不到的结果。总
- 一、题目描述题目实现:网络资源的断点续传功能。二、解题思路获取要下载的资源网址显示网络资源的大小上次读取到的字节位置以及未读取的字节数输入下
- // 十进制转化为十六进制,结果为C8。 Integer.toHexString(200); //十六进制转化为十进制,
- 1.application.yml中添加两个datasourceserver: port: 8080spring: application:
- 写在前面作为程序员,多多少少都会遇到一些内存溢出的场景,如果你还没遇到,说明你工作的年限可能比较短,或者你根本就是个假程序员!哈哈,开个玩笑