Spring中事务嵌套这么用一定得注意了!!
前言
最近项目上有一个使用事务相对复杂的业务场景报错了。在绝大多数情况下,都是风平浪静,没有问题。其实内在暗流涌动,在有些异常情况下就会报错,这种偶然性的问题很有可能就会在暴露到生产上造成事故,那究竟是怎么回事呢?
问题描述
我们用一个简单的例子模拟下,大家也可以看看下面这段代码输出的结果是什么。
- 在类
SecondTransactionService定义一个简单接口transaction2,插入一个用户,同时必然会抛出错误
@Override
@Transactional(rollbackFor = Exception.class)
public void transaction2() {
System.out.println("do transaction2.....");
User user = new User("tx2", "111", 18);
// 插入一个用户
userService.insertUser(user);
// 跑错了
throw new RuntimeException();
}
- 在另外一个类
FirstTransactionService定义一个接口transaction1,它调用transaction2方法,同时做了try catch处理
@Override
@Transactional(rollbackFor = Exception.class)
public void transaction1() {
System.out.println("do transaction1 .......");
try {
// 调用另外一个事务,try catch住
secondTransactionService.transaction2();
} catch (Exception e) {
e.printStackTrace();
}
// 插入当前用户tx1
User user = new User("tx1", "111", 18);
userService.insertUser(user);
}
- 定义一个
controller,调用transaction1方法
@GetMapping("/testNestedTx")
public String testNestedTx() {
firstTransactionService.transaction1();
return "success";
}
大家觉得调用这个http接口,最终数据库插入的是几条数据呢?
问题结果
正确答案是数据库插入了0条数据。
同时控制台也报错了,报错原因是:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
是否和你预想的一样呢?你知道是为什么吗?
原因追溯
其实原因很简单,我们都知道,一个事务要么全成功提交事务,要么失败全部回滚。如果出现在一个事务中部分SQL要回滚,部分SQL要提交,这不就主打的一个”前后矛盾,精神分裂“吗?
controller.testNestedTx()
||
/
FirstTransactionService.transaction1() REQUIRED隔离级别
||
||
|| 捕获异常,提交事务,出错啦
/ ||
FirstTransactionService.transaction2() REQUIRED隔离级别
|| ||
|| 抛出异常,标记事务为rollback only
=======================
- 事务的隔离级别为
REQUIRED,那么发现没有事务开启一个事务操作,有的话,就合并到这个事务中,所以transaction1()、transaction2()是在同一个事务中。 transaction2()抛出异常,那么事务会被标记为rollback only, 源码如下所示:
transaction1()由于try catch异常,正常运行,想必就要可以提交事务了,在提交事务的时候,会检查rollback标记,如果是true, 这时候就会抛出上面的异常了。源码如下图所示:
这下,是不是很清楚知道报错的原因了,那想想该怎么处理呢?
解决之道
知道了根本原因之后,是不是解决的方案就很明朗了,我们可以通过调整事务的传播方式分拆多个事务管理,或者让一个事务"前后一致",做一个诚信的好事务。
- 将
try catch放到内层事务中,也就是transaction2()方法中,这样内层事务会跟着外部事务进行提交或者回滚。
@Override
@Transactional(rollbackFor = Exception.class)
public void transaction2() {
try {
System.out.println("do transaction2.....");
User user = new User("tx2", "111", 18);
userService.insertUser2(user);
throw new RuntimeException();
} catch (Exception e) {
e.printStackTrace();
}
}
- 如果希望内层事务抛出异常时中断程序执行,直接在外层事务的
catch代码块中抛出e,这样同一个事务就都会回滚。 - 如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为
PROPAGATION_NESTED。PROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。
事务的传播机制
前面提到了事务的传播机制,我们再看都有哪几种。
PROPAGATION_REQUIRED:加入到当前事务中,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring中默认采用的方式。PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。PROPAGATION_REQUIRES_NEW:新建一个事务,如果当前存在事务,把当前事务挂起。PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
如何理解PROPAGATION_NESTED的传播机制呢,和PROPAGATION_REQUIRES_NEW又有什么区别呢?我们用一个例子说明白。
- 定义
serviceA.methodA()以PROPAGATION_REQUIRED修饰; - 定义
serviceB.methodB()以表格中三种方式修饰; methodA中调用methodB;
总结
在我的项目中之所以会报“rollback-only”异常的根本原因是代码风格不一致的原因。外层事务对错误的处理方式是返回true或false来告诉上游执行结果,而内层事务是通过抛出异常来告诉上游(这里指外层事务)执行结果,这种差异就导致了“rollback-only”异常。大家也可以去review自己项目中的代码,是不是也偷偷犯下同样的错误了。
欢迎关注个人公众号【JAVA旭阳】交流学习
Spring中事务嵌套这么用一定得注意了!!的更多相关文章
- Spring中事务的5种属性总结
Sping的事务 和 数据库的事务是不同的概念,数据库的事务一般称为底层事务 Spring的事务是对这种事务的抽象 我称之为逻辑事务 Spring对事务的功能进行了扩展,除了基本的Isolation之 ...
- 【面试普通人VS高手系列】Spring中事务的传播行为有哪些?
一个工作了2年的粉丝,私信了一个比较简单的问题. 说: "Spring中事务的传播行为有哪些?" 他说他能记得一些,但是在项目中基本上不需要配置,所以一下就忘记了. 结果导致面试被 ...
- spring对数据库的操作、spring中事务管理的介绍与操作
jdbcTemplate的入门 创建maven工程 此处省略 导入依赖 <!-- https://mvnrepository.com/artifact/org.springframework/s ...
- 04 Spring:01.Spring框架简介&&02.程序间耦合&&03.Spring的 IOC 和 DI&&08.面向切面编程 AOP&&10.Spring中事务控制
spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...
- Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现
Spring中事务的使用示例.属性及使用中可能出现的问题 前言 本专题大纲如下: 对于专题大纲我又做了调整哈,主要是希望专题的内容能够更丰富,更加详细,本来是想在源码分析的文章中附带讲一讲事务使用中的 ...
- 对于spring中事务@Transactional注解的理解
现在spring的配置都喜欢用注解,这边就说下@Transactional 一.如何开启@Transactional支持 要使用@Transactional,spring的配置文件applicatio ...
- Spring 中事务控制的API介绍
1.PlatformTransactionManager Spring所有事务代理类都是基于PlatformTransactionManager接口的实现. 此接口是spring的事务管理器,它里面提 ...
- Spring中事务配置以及事务不起作用可能出现的问题
前言:在Spring中可以通过对方法进行事务的配置,而不是像原来通过手动写代码的方式实现事务的操作,这在很大程度上减少了开发的难度,本文介绍Spring事务配置的两种方式:基于配置文件的方式和基于注解 ...
- 【转】Spring中事务与aop的先后顺序问题
[原文链接] http://my.oschina.net/HuifengWang/blog/304188 [正文] Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟sp ...
- 关于spring中事务管理的几件小事
1.Spring中的事务管理 作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层.而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制. S ...
随机推荐
- Qt之如何创建并显示一个柱状图
创建一个简单的柱状图 第一步:创建一个QBarSet对象:QBarSet类代表条形图中的一组条形. QBarSet *set0 = new QBarSet("Jane"); QBa ...
- YY播放器源码解析
YY播放器使用Flutter编写的一个聚合播放器, 起因是看了 ZY-Player的源码, 发现实现挺有意思的, 也比较简单 地址: https://github.com/waifu-project/ ...
- 标题Ubuntu将默认的python3改为默认的python
ryzen@cp7:~$ python Command 'python' not found, did you mean: command 'python3' from deb python3 roo ...
- pycharm安装包的简便方法
- windows微信如何双开
生活中存在同时使用两个微信的情况,一个工作一个生活,这时希望同时在电脑上登录两个账号.如何做到呢?步骤如下: 右键单击"微信"图标,选择属性,目标框内的路径就是微信安装路径,复制目 ...
- 关于如何在IDEA里面配置好Git和Github的相关步骤详解
配置Git 1.File->settings 然后选择其中的Version Control: 接着选择其中的Git选项: 2.将文本框内容定位到本地下载git路径下面 然后点击右边的Test测试 ...
- MD5 简介 以及 C# 和 js 实现【加密知多少系列】
〇.简介 MD5 是哈希算法(散列算法)的一种应用.Hash 算法虽然被称为算法,但实际上它更像是一种思想.Hash 算法没有一个固定的公式,只要符合散列思想的算法都可以被称为是 Hash 算法. 算 ...
- Linux & 标准C语言学习 <DAY5>
一.if分支语句 if(表达式) //单分支语句 { //表达式的值为真,则执行此处代码 } if(表达式) //双分支语句 { ...
- 当基础设施故障后,声网 SD-RTN™ 如何保障 RTE 服务的高可用性
云计算的出现为企业的管理.业务开展.资源整合等带来了极大的便利性,也是数字化建设的核心基建之一,然而局部宕机或者大面积宕机事件对于云厂商来说却也无法避免,全球领先的计算平台也不例外.例如,美国东部时间 ...
- Cesium近地天空盒,解决图片旋转问题
前言 当我们使用官网的例子设置天空盒后,会发现天空云彩是斜的,比如下边这张图:通过查阅网上资料,需要修改cesium的源码,以下是修改后skybox的源码,在自己的项目中引入即可: 实现代码 cons ...