盘点导致Spring事务失效的4个场景

news2024/11/17 0:05:51

1,非运行时异常导致事务无法回滚

我们知道,Spring是通过AOP的方式来实现事务的,而在处理事务的过程中,Spring只有捕获到RuntimeException或者Error的时候才会触发回滚操作,如果我们在代码中抛出的是非运行时异常,而又没有特殊配置的话,事务就会无法回滚。

下面我们以一个简单的例子,复现一下这种情况,以及针对这种情况的解决方案。

本文Springboot版本:2.7.6,数据源为MySQL。

首先创建一个测试用的User对象:

@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private String pwd;
}
复制代码

建表语句:

CREATE TABLE user  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `pwd` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB;
复制代码

测试逻辑:往user表插入一条数据,如果插入成功,就抛出exception异常,测试数据是否回滚。

@Service
@AllArgsConstructor
public class DemoService {
    private final UserMapper userMapper;
  
    @Transactional
    public ResponseEntity<Object> addUser(User user) throws Exception {
        int insert = this.userMapper.insert(user);
        if (insert > 0) {
            throw new Exception("异常回滚测试");
        }
        return ResponseEntity.ok().build();
    }
}
复制代码

新建测试方法:

@Test
public void addUserTest() throws Exception {
    User user = new User();
    user.setName("测试");
    user.setPwd("123456");
    this.demoService.addUser(user);
}
复制代码

运行测试方法,从控制台可以看到,我们手动指定的异常被成功抛出。

但是,当异常发生时,事务并没有被回滚,数据依然被插入到了数据库。

解决办法:
1,将异常包装成运行时异常:throw new RuntimeException("异常回滚测试");

2,在@Transactional指定回滚的异常类型,@Transactional(rollbackFor = Exception.class)。

一般来说,使用第二种方式会更清晰一些,但是有些朋友往往会忘记手动指定回滚的异常类型,进而导致非预期的bug产生。

2,通过this调用本类事务方法导致的事务无法回滚

随着业务的发展,核心业务代码会越来越多,同一个方法也会越写越长。我们为了使代码逻辑更加高内聚低耦合,会将功能相同的代码进行封装成一个个的子方法。

但是,如果我们对事务的运行机制了解不透彻,随意在同一个类中通过this调用事务方法,就可能导致非预期的bug。

@Service
@RequiredArgsConstructor
public class DemoService {
    private final UserMapper userMapper;
    public ResponseEntity<Object> addUser(User user){
        //注意这一行
        this.doAddUser(user);
        return ResponseEntity.ok().build();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doAddUser(User user) {
        int insert = this.userMapper.insert(user);
        if (insert > 0) {
            throw new RuntimeException("测试添加异常回滚");
        }
    }
}
复制代码

如以上代码所示,在addUser方法中调用了事务方法doAddUser,如果数据插入成功,就抛出一个异常,测试数据是否能够回滚。

通过测试用例可以看到,异常已经抛出,但是数据库中却成功的插入了数据,我们期望的数据并没有回滚。

原因探究:

原因其实很简单,通过this方法调用时,Spring的代理没能起作用,事务自然也就无法介入,关于这一点的原理在之前的文章中也有分析过,感兴趣的朋友可以去看一看。

有的朋友可能会说,项目的代码已经是这样了,再将老方法重写到新类中也不现实,有没有办法改动较小的方式呢?

其实很简单,现在事务失效的原因是代理失效,那么想办法让代理重新生效就行了。

我们在本类中注入一个当前对象,这个对象可以被Spring代理,那么这个对象的方法自然也可以被代理。

@Service
@RequiredArgsConstructor
public class DemoService {
    private final UserMapper userMapper;
    @Resource
    private DemoService self;
    public ResponseEntity<Object> addUser(User user){
        //通过self引用使代理生效
        this.self.doAddUser(user);
        return ResponseEntity.ok().build();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doAddUser(User user) {
        int insert = this.userMapper.insert(user);
        if (insert > 0) {
            throw new RuntimeException("测试添加异常回滚");
        }
    }
}
复制代码

3,被声明的事务方法是private类型

这种错误在博主刚工作时遇到挺多次的,不过现在现代IDE已经越来越智能了,对于这种情况会直接给出错误提示,所以这里提出这种错误只是告诉大家,事务方法是不能声明为private的。

至于为什么不能是private,那自然还是和代理有关了。

4,嵌套事务异常导致事务被提前关闭而报错

当使用嵌套事务时,需要明确指定事务的传播范围。

@Service
@RequiredArgsConstructor
public class DemoService {
    private final UserMapper userMapper;
    @Resource
    private DemoService self;

    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<Object> addUser(User user) {
        int insert = this.userMapper.insert(user);
        if (insert > 0) {
            try {
                this.self.update(user);
            } catch (Exception e) {
                System.out.println("即使更新异常也不要影响添加数据");
            }
        }
        return ResponseEntity.ok().build();
    }

    @Transactional(rollbackFor = Exception.class)
    public void update(User user) {
        user.setPwd("666666");
        int update = this.userMapper.updateById(user);
        if (update > 0) {
            throw new RuntimeException("测试更新数据回滚");
        }
    }
}
复制代码

如以上代码,我们添加完一条数据之后,尝试将密码更新为666666,并且希望即使更新异常,也不要影响添加操作。

然而运行测试用例,我们会得到这样一条错误信息:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。

什么意思呢?就是当Spring处理事务时,发现事务已经被回滚了。

这是因为我们并没有指定事务的传播行为,默认情况下,Spring的事务传播是REQUIRED,即:如果本来有事务,则加入该事务,如果没有事务,则创建新的事务。

我们添加数据时启动了一个事务,更新数据时,Spring判断当前已经存在事务,所以就不再新建事务,而是加入当前事务。

但是当更新操作失败时,需要对事务进行回滚,更新是没问题的,正常回滚。

但是插入操作就不行了,当要提交插入操作的事务时,由于事务已经被回滚了,无法再次操作,Spring只好报错来提示我们了。

如何处理呢?在更新操作上指明事务的传播范围就行。

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void update(User user) {
  user.setPwd("666666");
  int update = this.userMapper.updateById(user);
  if (update > 0) {
    throw new RuntimeException("测试更新数据回滚");
  }
}
复制代码

再测试一下,发现插入操作的事务可以正常提交了。

总结

事务是我们日常开发工作中无法避免的一个功能,深刻理解事务的运行机制,正确使用事务的声明式操作,才能让我们写出更健壮的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/107829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web前端学习之虚拟DOM如何进化为真实DOM

Vue和React的Render函数中都涉及到了Virtual DOM的概念&#xff0c;Virtual DOM也是性能优化上的重要一环&#xff0c;同时突破了直接操作真实DOM的瓶颈&#xff0c;本文带着以下几个问题来阐述Virtual DOM。 1.为什么要操作虚拟 DOM? 2.什么是虚拟 DOM? 3.手把手教你实现…

Word内容解析之图表数据获取

最近遇到一个问题&#xff0c;Word里有个从Excel直接复制进去的图&#xff0c;但那个Excel已经找不到了&#xff0c;无法通过编辑数据获取到表格的数据。这个其实可以用getdata等软件获取&#xff0c;或者鼠标点在表上的点就可以显示数据&#xff0c;再把数据录下来&#xff0c…

更加灵活、经济、高效的训练 — 新一代搜推广稀疏大模型训练范式GBA

作者&#xff1a;苏文博、张远行 近日&#xff0c;阿里巴巴在国际顶级机器学习会议NeurIPS 2022上发表了新的自研训练模式 Gloabl Batch gradients Aggregation&#xff08;GBA&#xff0c;论文链接&#xff1a;https://arxiv.org/abs/2205.11048&#xff09;&#xff0c;由阿里…

模拟电子技术(七)波形的发生和信号的转换

&#xff08;七&#xff09;波形的发生和信号的转换正弦波振荡电路RC正弦波振荡电路LC正弦波振荡电路正弦波振荡例题电压比较器单限比较器过零比较器一般单限比较器滞回比较器窗口比较器电压比较器例题非正弦波发生电路矩形波发生电路三角波发生电路锯齿波发生电路信号转换电路…

Visual Studio 调试无法启动调试,拒绝访问

方法一 win更新了不兼容 &#xff0c;卸载更新。 1、单击开始菜单&#xff0c;选择【设置】如下图&#xff1b; 2、然后再进入【更新和安全】选项&#xff0c;如下图&#xff1b; 3、查看已安装更新历史记录&#xff0c;如下图红圈 4、这个页面详细列出了最新的更新&#xf…

绿盟SecXOps安全智能分析技术白皮书 安全分析模型核心服务部署

安全分析模型核心服务部署 ModelOps 对所有的人工智能 模型&#xff08;图形模型、语言模型、基于规则的模型&#xff09;以及决策模型的整个生命周期 进行管理&#xff0c;确保对生产中的所有模型进行独立验证和问责&#xff0c;其核心功能涵盖了模型存储、模型测试、模型回滚…

28. 如何使用 SAP OData 服务向 ABAP 服务器上传文件

文章目录 1. 创建对应的自定义数据库表和 ABAP DDIC 结构2. 完成 SEGW 事物码里模型的增强3. 完成必要的 ABAP 编码本教程到目前为止开发的 OData 图书管理服务,可以在 ABAP 系统里对图书数据进行增删改查。 本步骤我们继续介绍如何通过 SAP OData 服务,实现向 ABAP 系统上传…

0.96寸OLED显示屏介绍

OLED显示屏简介 OLED&#xff0c;即有机发光二极管&#xff08;Organic Light Emitting Diode&#xff09;。OLED 由于同时具备自发光&#xff0c;不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性&#x…

【C语言】常见字符函数和字符串函数

1.1strlen size_t strlen(const char* str); 字符串已经\0作为结束标志&#xff0c;strlen函数返回的是在字符串中\0前面出现的字符个数&#xff08;不包含\0&#xff09;。 参数指向的字符串必须以\0结束。 注意函数的返回值为size_t&#xff0c;是无符号整形&#xff08;…

五、Vector底层源码详解

文章目录特点底层源码分析有参构造器public Vector(int initialCapacity, int capacityIncrement)有参构造器public Vector(int initialCapacity)有参构造器public Vector(Collection<? extends E> c)无参构造器public Vector()扩容机制特点 底层是elementDate数组线程…

自学Python真的可以吗?

自学当然可以学成功python了&#xff0c;但是前提是你需要认真去学&#xff0c;而不是三天打渔两天晒网的&#xff0c;因为python初学很容易&#xff0c;稍微过几天忘记也很容易&#xff0c;所以一定要坚持学习&#xff0c;并且通过平时多加练习来熟练掌握各个知识点。 一、学…

非零基础自学Golang 第15章 Go命令行工具 15.4 注释文档(doc)

非零基础自学Golang 文章目录非零基础自学Golang第15章 Go命令行工具15.4 注释文档(doc)第15章 Go命令行工具 15.4 注释文档(doc) Go语言文档工具go doc和go fmt一样&#xff0c;也是对godoc的简单封装。 我们通常使用go doc查看指定包的文档。 例如我们查看函数fmt.Println…

焕然一新的 Vue 3 中文文档要来了

前言 大家好&#xff0c;我是LBJ&#xff0c;最近参与了 Vue 3新文档的翻译和校验工作 (vuejs/docs contributor 和 docs-zh-cn contributor) 我们知道 Vue 3 新文档 ( vuejs.org ) 已经发布一个多月了&#xff0c;但那是英文版的&#xff0c;不知道你看了没&#xff1f; 没…

恒业微晶冲刺深交所:年营收4.3亿 戴联平控制73.8%表决权

雷递网 雷建平 12月20日上海恒业微晶材料科技股份有限公司&#xff08;简称&#xff1a;“恒业微晶”&#xff09;日前递交招股书&#xff0c;准备在深交所创业板上市。恒业微晶计划募资8亿元&#xff0c;用于恒业新型分子筛项目。年营收4.31亿恒业微晶创建于1992年&#xff0c…

Vue3 Proxy代理为什么要用 Reflect映射

瞅一眼Vue3源码 地址&#xff1a;https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts 可以看到Proxy响应式代理 依赖 createGetter与createSetter方法&#xff1a; &#x1f6a5; createGetter function createGetter(isReadonly false, s…

Go并发大坑:inconsistent mutex state 解决及心得体会

文章目录前言首次排查&#xff08;未重视&#xff09;问题逐渐严重加大排查力度增加锁日志race detector一次意外的复现写在最后解决问题所必需的品格前言 大概在这篇文章发布数个月之前&#xff0c;某天&#xff0c;线上稳定运行的Go服务突然毫无征兆的发生了一次重启&#x…

MyBatis的相应API与传统和代理开发的Dao层实现

MyBatis的相应API 1、SqlSession工厂构建器SqlSessionFactoryBuilder 常用API&#xff1a;SqlSessionFactory build(InputStream inputStream) 通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象 其中&#xff0c;mybatis-config.xml是类加载器的路径&…

为什么要学编程?为什么是Python?怎么学?

今天我们聊了为什么要去学编程&#xff0c;学编程为什么要选择Python。上大学的时候我学的信息管理&#xff0c;毕业后选择了和网络相关的网络安全公司做售后。 从上学的时候自己就想过去学学编程&#xff0c;感觉他们敲代码都敲的那么酷&#xff0c;看着满屏的代码还有命令行…

2022广航蓝桥杯选拔赛压轴题:取数博弈游戏

原题链接 对于这种「判断先手后手的必胜必败」的题目&#xff0c;博弈论方向是一个优先考虑的方向。 博弈论的重要思想就是决策者都要做出全局最优的决策而非局部最优&#xff0c;就好比专业的棋手走一步看五步&#xff0c;而博弈论则要求棋手走一步看无限步。 解题思路 凡…

[洛谷]P3613 【深基15.例2】寄包柜

[洛谷]P3613 【深基15.例2】寄包柜一、问题描述题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示二、思路分析1、算法标签2、算法思路三、代码实现一、问题描述 [洛谷]P3613 【深基15.例2】寄包柜 题目描述 超市里有 n(1≤n≤105)n(1\le n\le10^5)n(1≤n≤105) 个…