为了不再被事务坑,我读透了Spring的事务传播性。

在之前文章中,我们已经被事务坑了两次:

mq发送消息之后,业务代码回滚,导致发了一条中奖消息给用户!!

我又被Spring的事务坑了,用户兑奖之后,什么东西都没收到!!

所以作者对Spring的事务深恶痛绝,这次我们来再次对spring的事务发起进攻,还是用用户中奖这个例子去解释这个Spring的事务(用户:麻烦给一下出场费),防止以后再次出现这样的情况,这次我们的攻击点就是spring的事务的七种传播性。

Spring框架中的事务传播性是指当一个事务方法被另一个事务方法调用时,如何处理这种嵌套调用的情况。

虽然在上一篇文章中我们说到:一个Transactional注解方法就是一个mysql里面的事务,但是这里大家不要把Spring的事务和mysql的隔离级别搞混了。

我们先来看一个简单的例子,有一个接口是用户中奖的接口:

    /*** 中奖*/@GetMapping("/winning")public String winning(@RequestParam Integer userId) {return userService.winning(userId);}

image-20240808160641579

这里面是他的实现类,在这里,除了往user表中插入了一条数据,还往中奖记录表中添加了一条数据。我们这里抛出了一个异常用来模拟我们在业务处理中遇到的异常。

    @Overridepublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);throw new RuntimeException("fu*k Transactional");}

image-20240808160734077

    @Overridepublic void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);}

image-20240808160801799

下面是两张表的样子(demo演示,不会有这么简单的表的):

中奖用户表

image-20240805195234627

中奖记录表

image-20240805195254275

这个时候我们来模拟用户中奖:

image-20240805200815719

image-20240805200914028

可以看到,我们的后台已经报异常了,但是这个时候我们看我们的表数据就会发现,居然两条数据都插入进去了:

image-20240805201151898

这就有问题了呀,兄弟,哥们!!就会出现我们上面的情况:

我又被Spring的事务坑了,用户兑奖之后,什么东西都没收到!!

1. PROPAGATION_REQUIRED (默认值)

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

如果当前存在事务,则加入该事务:

这个是Transactional注解的默认值,也就是说你就写一个@Transactional,那么默认就是这个,所以我们把方法改为下面的试试:

 @Override@Transactionalpublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);throw new RuntimeException("fu*k Transactional");}@Override@Transactionalpublic void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);}

这个时候我们调用接口会出现什么情况呢?我们来试一下

image-20240806195407438

没错,他回滚了。也就是说没有执行,也就是说在外面的winning方法中是一整个大事务,只要事务里面报错了,则都会不commit事务。

image-20240806195636896

其实把异常放到这里也是可以全部回滚的:

    @Override@Transactionalpublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);return "ok";}@Override@Transactionalpublic void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

但是估计有时候大家也是这么写的:

@Override
@Transactional
public String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winning2(userId);throw new RuntimeException("fu*k Transactional");
}@Autowired
private WinningDao winningDao;public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);
}

这样写的后果就是事务也会回滚,(什么鬼?这个不是自己调用自己的方法吗,上次你不是说这种情况会失效吗?

我又被Spring的事务坑了,用户兑奖之后,什么东西都没收到!!)

盆友,稍安勿躁,且听我细细道来

之所以上面的方法也会回滚,是因为在外面本来就开启了一个事务,在调用自己的方法过程中其实都是在一个事务里面执行的,这并没有什么不妥,也照样走的是spring的代理,因为是winning是通过controller层调用进来的,所以自然是走了spring的代理,所以说,我们下面这样写结果也是会回滚的:

@Override
@Transactional
public String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winning2(userId);throw new RuntimeException("fu*k Transactional");
}@Autowired
private WinningDao winningDao;@Transactional
public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);
}

那什么情况下才符合我们之前说过的我又被Spring的事务坑了,用户兑奖之后,什么东西都没收到!!第三种情况自己调用自己就会失效呢?

这还不简单,你只要了解其中的原理就可以写出来了:

image-20240808161628514

@Override
public void  winningNoTransactional(Integer userId) {winning(userId);
}

image-20240808161653760

可以看到我们上面这样写,虽然winning有一个事务,但是他的上一层是自己类里面的方法调用自己的,相当于没走spring的代理,有的@Transactional和没的一样,所以自然不会生效了

image-20240808161933637

如果当前没有事务,则创建一个新的事务:

@Override
public String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning(userId);throw new RuntimeException("fu*k Transactional");
}@Override@Transactionalpublic void winning(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);}

那么这样的会回滚吗?答案是都插进去了:

image-20240808155415889

所以这里我们要注意一下,这样的写法不行的,因为第一个没有事务,第二个事务里面没有报异常,所以后面报了异常事务是不会回滚的,我们可以这样写:

@Override
@Transactional
public void winning(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");
}

image-20240808155742825

这个结果是user表插入成功,但是中奖表没有插入成功,但是这个符合我们的这个事务传播行为:如果当前没有事务,则创建一个新的事务

2.PROPAGATION_SUPPORTS

  • 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。

如果当前存在事务,则加入该事务

@Override
@Transactional
public String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);throw new RuntimeException("fu*k Transactional");
}@Override@Transactional(propagation = Propagation.SUPPORTS)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);}

这个结果显而易见是不会插入成功的:

image-20240808164050974

如果当前没有事务,则以非事务的方式继续执行

    @Overridepublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);throw new RuntimeException("fu*k Transactional");}@Override@Transactional(propagation = Propagation.SUPPORTS)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

可以看到是成功插入了,因为winning没有事务,winning2的传播行为也不会加入事务,即使抛出了异常,也不会回滚。

image-20240808164451029

3.PROPAGATION_MANDATORY

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

如果当前存在事务,则加入该事务.

这个和第一点第二点是一样的(讲个锤子)

如果当前没有事务,则抛出异常

    @Overridepublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);throw new RuntimeException("fu*k Transactional");}@Override@Transactional(propagation = Propagation.MANDATORY)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

这个传播行为很有用,例如你写一个公用的方法给其他同事调用的话可以致使他的方法必须拥有事务。

image-20240808165614936

但是结果呢?显而易见,第一张表插入了,第二张没插入(因为第二个方法都没进来就报错了,第一个没有事务,自然插入成功了)

image-20240808165807029

4.PROPAGATION_REQUIRES_NEW

  • 创建一个新的事务,并且挂起当前的事务(如果存在的话)。
    @Override@Transactionalpublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);return "ok";}@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

这个结果就是两个都没插入,这个很好理解,但是可能大家不理解挂起的概念

解释:

  1. winning 方法:当调用 winning 方法时,一个新的事务(记为事务A)开始。
  2. 插入用户:在事务A中,User 对象被创建并插入数据库。
  3. 调用 winning2 方法:接着调用 winning2 方法,这将启动一个新的事务(记为事务B),并且事务A被挂起。
  4. 插入 Winning 记录:在事务B中,Winning 对象被创建并插入数据库。
  5. 抛出异常:在事务B中抛出 RuntimeException,导致事务B回滚,撤销所有更改。
  6. 恢复事务A:事务B结束后,事务A被恢复。

如果 winning2 方法中的异常没有被捕获,并且传播到了 winning 方法中,那么 winning 方法中的事务A也会被回滚,所以如果我们想 winning 方法里面的user真正插入的话,我们就可以这样写:

@Override
@Transactional
public String winning(Integer userId) {try {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);} catch (Exception e) {log.error("Exception occurred in winning method: ", e);// 这里可以选择记录错误信息,或者做一些其他的错误处理// 但是不需要在这里回滚事务,因为Spring会自动处理}return "ok";
}@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");
}

这样大家是不是就理解了创建一个新的事务,并且挂起当前的事务这句话了

5.PROPAGATION_NOT_SUPPORTED

  • 以非事务方式执行操作,并挂起当前事务(如果存在的话)。
    @Override@Transactionalpublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);return "ok";}@Override@Transactional(propagation = Propagation.NOT_SUPPORTED)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

执行流程

  1. winning 方法开始:当调用 winning 方法时,一个新的事务(记为事务A)开始。
  2. 插入用户:在事务A中,User 对象被创建并插入数据库。
  3. 调用 winning2 方法:接着调用 winning2 方法。由于 winning2 方法使用了 Propagation.NOT_SUPPORTED,这意味着:
  4. 如果当前存在事务(即事务A),则该方法不在任何事务中执行。
  5. 如果当前不存在事务,则该方法同样不在任何事务中执行。
  6. 插入 Winning 记录:在 winning2 方法中,Winning 对象被创建并插入数据库。由于 winning2 方法不在事务中执行,因此数据库操作直接提交,不会等待事务结束。
  7. 抛出异常:在 winning2 方法中抛出 RuntimeException。
  8. 异常处理:由于 winning2 方法不在事务中执行,异常直接抛出给调用者 winning 方法。
  9. 异常传播:如果 winning 方法没有捕获这个异常,那么异常会继续向上层传播,导致事务A回滚。

所以结果是,winning表插入了,user表没有:

image-20240808173551550

如果为了确保 User 数据能够被正确插入,同时避免事务A因 winning2 方法中的异常而回滚,我们也可以在 winning 方法中捕获异常,以确保事务A能够正常提交。

6.PROPAGATION_NEVER

  • 以非事务方式执行,如果当前存在事务,则抛出异常。

image-20240808173844001

这个传播行为也很有意思,如果当前存在事务,则抛出IllegalTransactionStateException异常

和第三点PROPAGATION_MANDATORY完全相反的,而且是直接以非事务方式执行

7.PROPAGATION_NESTED

  • 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则其行为类似于PROPAGATION_REQUIRED

如果当前存在事务,则在嵌套事务内执行

    @Override@Transactionalpublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);return "ok";}@Override@Transactional(propagation = Propagation.NESTED)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

结果是两个表都没插入,但是什么叫嵌套事务呢?

嵌套事务是事务winning的一个子事务(记为事务winning2),它与事务winning共享相同的资源,但有自己的保存点。

如果当前没有事务,则其行为类似于PROPAGATION_REQUIRED

也就是说类似于我们的第一种:如果当前没有事务,则创建一个新的事务

    @Overridepublic String winning(Integer userId) {User user = new User();user.setId(userId);user.setUsername("user" + userId);userDao.insert(user);winningService.winning2(userId);return "ok";}@Override@Transactional(propagation = Propagation.NESTED)public void winning2(Integer userId) {Winning winning = new Winning();winning.setUserId(userId);winningDao.insert(winning);throw new RuntimeException("fu*k Transactional2");}

结果就是user新建了,winning表没有新建

image-20240808194941233

相信仔细看完的同学已经注意到了里面有很多名词:加入事务,挂起事务,嵌套事务

这里做一个简单的总结,更详细的这里不做研究(性价比不高,很少用得到,上面7总如果开发用到了,根据实际情况调整即可)

加入事务REQUIRED 加入当前事务,或者创建一个新的事务。

挂起事务REQUIRES_NEW 挂起当前事务,创建一个新的事务执行方法。

嵌套事务NESTED 在现有事务内创建一个子事务,可以独立回滚至 Savepoint

在这里插入图片描述

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

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

相关文章

【高阶用法】uniapp的i18n/修复/增强/App无重启更换语言

痛点 在i18n多语言模块使用过程中,发现下面几个问题,需要解决 1)uni-best框架下,$t功能函数无法实时的切换语言,可能跟使用有关 2)uni-best建议的translate方式在vue块外使用太繁琐,希望不用…

10年计算机考研408-计算机网络

【题33】下列选项中,不属于网络体系结构所描述的内容是() A.网络的层次 B.每一层使用的协议 C.协议的内部实现细节 D.每一层必须完成的功能 解析: 本题考查的是网络体系结构相关的概念。 图1描述了网络的7层架构以及每一层所要完成…

防火墙详解(一) 网络防火墙简介

原文链接:https://blog.csdn.net/qq_46254436/article/details/105519624 文章目录 定义 与路由器和交换机的区别 发展历史 防火墙安全区域 定义 防火墙主要用于保护一个网络区域免受来自另一个网络区域的网络攻击和网络入侵行为 “防火墙”一词起源于建筑领域&…

Openai gym environment for multi-agent games

题意:用于多智能体游戏的 OpenAI Gym 环境 问题背景: Is it possible to use openais gym environments for multi-agent games? Specifically, I would like to model a card game with four players (agents). The player scoring a turn starts the…

8月份工业机器人产量同比增长20%

近日,国家统计局公布数据显示,8月份,我国工业机器人产量为47947套,较去年同期增长20%;1-8月份,总产量为360592套,较去年同期增长9.9%。 9月14日,国家统计局发布数据显示,…

十大常用加密软件排行榜|2024年好用的加密软件推荐【精选】

在信息安全日益重要的时代,加密软件成为保护个人和企业数据的关键工具。选择合适的加密软件可以有效防止数据泄露和未授权访问。以下是2024年值得推荐的十大加密软件,帮助你找到适合的解决方案。 1. Ping32加密软件 Ping32是一款功能强大的加密软件&…

Spring Boot 学习之路 -- 处理 HTTP 请求

前言 最近因为业务需要,被拉去研究后端的项目,代码基于 Spring Boot,对我来说完全小白,需要重新学习研究…出于个人习惯,会以 Blog 文章的方式做一些记录,文章内容基本来源于「 Spring Boot 从入门到精通&…

【数列求值 / B】

题目 一般做法 #include <bits/stdc.h> using namespace std; const int mod 10000; int f[20190325] {1, 1, 1, 1}; int main() {for(int i 4; i < 20190324; i){f[i] (f[i-1] f[i-2] f[i-3]) % mod;}cout << f[20190324]; } 快速幂矩阵乘法 #includ…

索迪迈车载监控设备的优势有哪些

在当今社会&#xff0c;车载监控设备已经成为保障公共安全与交通管理的重要工具。索迪迈车载监控设备&#xff0c;以其先进的技术和卓越的性能&#xff0c;成为业界的佼佼者。其优势主要体现在以下几个方面&#xff1a; 一、抽拔式硬盘设计 1. 便捷的数据管理 车载监控设备需…

Rk628D 在 RK3588s平台上的驱动移植

硬件平台: W1_AI_RK3588S_V0 处理器: rk3588s kernel版本: Linux version 5.10.110 芯片是:rk628D 目的是:(4k)HDMI输入mipi 输出 1、下载RK628 最新(2024.09)的代码链接: 通过百度网盘分享的文件:RK628 链接:https://pan.baidu.com/s/1zN9yD2FQWAzVUMY1op…

Java面试题大全(全网最全,持续更新)初级(2)

1. 基础语法 1.1. Java 的数据类型有哪些&#xff1f; Java 有两种数据类型&#xff1a; 基本数据类型&#xff08;Primitive Types&#xff09;&#xff1a;包括 byte、short、int、long、float、double、char、boolean。引用数据类型&#xff08;Reference Types&#xff…

环境领域顶刊EST发表!又一次颠覆性突破!

2023年3月21日&#xff0c;普林斯顿大学任智勇教授团队针对最近爆火的ChatGPT和环境研究的交叉在环境领域顶级期刊《Environmental Science & Technology》发表了观点类文章“ChatGPT and Environmental Research”。 任智勇教授中对未来的展望表示&#xff1a; 颠覆性技术…

便携式气象观测仪的工作原理

TH-PQX9】便携式气象观测仪是一种集多种气象要素观测于一体&#xff0c;便于携带和使用的小型气象观测设备。实时监测和记录多种气象要素&#xff0c;包括温度、湿度、风速、风向、气压、太阳辐射、雨量等&#xff0c;满足不同场景下的气象监测需求。采用高精度传感器&#xff…

平板电容笔哪个牌子好?精选电容笔品牌排行榜前五名推荐!

在当今时代&#xff0c;平板电容笔已经成为平板电脑的重要配件&#xff0c;为人们的学习、工作和创作带来了极大的便利。然而&#xff0c;市场上平板电容笔的品牌众多&#xff0c;质量和性能也参差不齐&#xff0c;这让消费者在选择时常常感到困惑。平板电容笔究竟哪个牌子更好…

计算n个节点所能组成的不同二叉搜索树(卡特兰数)

计算n个节点所能组成的不同二叉搜索树的时候我们一般都是画图&#xff0c;但是有一个拱墅可以快速计算

概率论与数理统计(持续更新)

一.概率论基本概念 1.确定性现象与非确定性现象 确定性现象&#xff0c;具有事前可预言性 非确定性现象&#xff0c;具有事前不可预言性 2.随机现象&#xff0c;在个别实验中具有不确定性&#xff0c;在大量重复实验中呈现规律性 统计规律性&#xff0c;大量同类随机现象所…

TCP 协议机制超详解

我的主页&#xff1a;2的n次方_ 1. 协议结构 2. 确认应答 在之前提到过 TCP 的核心机制是确认应答&#xff0c;可以确认对方是否收到数据&#xff0c;在数据传输的过程中&#xff0c;如果有多条请求&#xff0c;并且返回对应的响应&#xff0c;但是此时可能会出现这样的问题…

【通俗易懂】知识图谱增强 RAG 思路 和 实现方案

【通俗易懂】知识图谱增强 RAG 思路 和 实现方案 为什么用 知识图谱增强 RAG&#xff1f;对比传统方法3 种实现方式 方案一&#xff1a;利用 KG 关系网络&#xff0c;构建问题子图促精准解答地图固定深度整体优化方案 方案二&#xff1a;利用 KG 语义关联&#xff0c;提升文档片…

【重学 MySQL】三十八、group by的使用

【重学 MySQL】三十八、group by的使用 基本语法示例示例 1: 计算每个部门的员工数示例 2: 计算每个部门的平均工资示例 3: 结合 WHERE 子句 WITH ROLLUP基本用法示例注意事项 注意事项 GROUP BY 是 SQL 中一个非常重要的子句&#xff0c;它通常与聚合函数&#xff08;如 COUNT…

C++ -缺省参数-详解

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【C】 欢迎点赞&#x1f44d;收藏⭐关注❤️ C -缺省参数-详解 1.是什么2.分类2.1全缺省参数2.2半缺省参数&#xff1a; 3.实际应用4.关于缺省参数的声明与定义5.总结 1.是什么 先来看看下面这段代码&#xff1a; #incl…