事务传播行为Propagation

news2024/11/24 3:27:42

目录

  • 背景
  • Propagation
  • 测试程序1
  • 测试程序2
  • 分析

背景

前段时间,某个项目在部署时,被公司的一个检测拦截了,提示报错如下:

Your code exists Method or Class with @Transactional annotation that not use Propagation.REQUIRED.

在这里插入图片描述
有1个方法加了常见的@Transactional注解,但是事务传播行为设置的不是默认的Propagation.REQUIRED,而是Propagation.REQUIRES_NEW。
那么,默认的Propagation.REQUIRED做了些啥呢,不设置为这个又有什么隐患呢?
关于@Transactional注解,之前也写过一篇文章: @Transational踩坑。

Propagation

Propagation是org.springframework.transaction.annotation包下的枚举,一共有7种。

级别英文释义中文释义是否获取新链接
REQUIREDSupport a current transaction, create a new one if none exists. This is the default setting of a transaction annotation.支持当前事务,如果不存在,则创建一个新事务。这是事务注释的默认设置。×
REQUIRES_NEWCreate a new transaction, and suspend the current transaction if one exists.创建一个新事务,如果存在当前事务,则暂停该事务。
SUPPORTSSupport a current transaction, execute non-transactionally if none exists.支持当前事务,如果不存在,则以非事务方式执行。×
NOT_SUPPORTEDExecute non-transactionally, suspend the current transaction if one exists.以非事务方式执行,如果存在当前事务,则暂停当前事务。
NEVERExecute non-transactionally, throw an exception if a transaction exists.以非事务方式执行,如果存在事务,则引发异常。×
MANDATORYSupport a current transaction, throw an exception if none exists.支持当前事务,如果不存在则抛出异常。×
NESTEDExecute within a nested transaction if a current transaction exists, behave like REQUIRED otherwise.如果当前事务存在,则在嵌套事务中执行,否则行为类似于 REQUIRED。×

上面的表格中在描述事务传播行为时,都有前置条件,那就是是否自己是一个嵌套事务。也就是调用本方法的上层方法是否是一个事务,下面仔细分析在嵌套事务下的各种行为。

测试程序1

插入user表,2个用户,user1.年龄=10,user2.年龄=20;ClassA更新user1.年龄=11(事务,默认的Propagation.REQUIRED),ClassB更新user1.年龄=12(事务,测试不同的Propagation);ClassA和ClassB构成嵌套事务。

@Component
@RequiredArgsConstructor
public class Test {
    private final ClassA classA;

    public void test(Propagation propagation) {
        // insert into user, user_name = 'user1', age = 10;
        // insert into user, user_name = 'user2', age = 20;

        classA.updateUserAge(propagation, "user1", "user2");
    }
}
@Component
@RequiredArgsConstructor
@Slf4j
public class ClassA {
    private final ClassB classB;

    @Transactional
    public void updateUserAge(Propagation propagation, String userName1, String userName2) {
        // update user set age = 11 where user_name = userName1

        try {
            switch (propagation) {
                case REQUIRED:
                    classB.required(userName1, userName2);
                    return;
                case REQUIRES_NEW:
                    classB.requiresNew(userName1, userName2);
                    return;
                case SUPPORTS:
                    classB.supports(userName1, userName2);
                    return;
                case NOT_SUPPORTED:
                    classB.notSupported(userName1, userName2);
                    return;
                case NEVER:
                    classB.never(userName1, userName2);
                    return;
                case MANDATORY:
                    classB.mandatory(userName1, userName2);
                    return;
                case NESTED:
                    classB.nested(userName1, userName2);
                    return;
                default:
            }
        } catch (Exception e) {
            log.error("ClassA#updateUserAge error: {}", e.getMessage(), e);
        }
    }
}
@Component
@RequiredArgsConstructor
public class ClassB {
    @Transactional(propagation = Propagation.REQUIRED)
    public void required(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requiresNew(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public void supports(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void notSupported(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    @Transactional(propagation = Propagation.NEVER)
    public void never(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void mandatory(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    @Transactional(propagation = Propagation.NESTED)
    public void nested(String userName1, String userName2) {
        updateUserAge(userName1, userName2);
    }

    private void updateUserAge(String userName1, String userName2) {
        // update user set age = 12 where user_name = userName1

        throw new RuntimeException("ClassB#updateUserAge RuntimeException");
    }
}

测试结果:

行为报错信息执行结果
REQUIREDorg.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only年龄未被更新
REQUIRES_NEWcom.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction年龄被更新=11(执行了ClassA.SQL,未执行ClassB.SQL)
SUPPORTS同REQUIRED同REQUIRED
NOT_SUPPORTED同REQUIRES_NEW同REQUIRES_NEW
NEVERorg.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’同REQUIRES_NEW
MANDATORY同REQUIRED同REQUIRED
NESTEDorg.springframework.transaction.CannotCreateTransactionException: Could not create JDBC savepoint; nested exception is java.sql.SQLFeatureNotSupportedException: setSavepoint name同REQUIRES_NEW
  1. REQUIRED
    由于ClassB本身会发生异常,所以ClassB中的update会进行回滚,并且由于REQUIRED是复用父事务,所以链接也是同一个,在回滚时,会将ClassA中的update也进行回滚。
    回滚完毕之后,会将异常抛至ClassA中,并被捕获,正常运行结束,进行提交时由于已经在ClassB中进行了回滚,所以会抛出异常UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。
  2. REQUIRES_NEW
    进入ClassB时,REQUIRES_NEW会获取新的链接,并且update的是同一条数据,会获取锁超时,导致ClassB的update会被回滚。
    剩下的和REQUIRED一样,只是在最后提交的时候,由于ClassB是新起一个事务,所以ClassA不会出异常,成功update年龄=11。
  3. SUPPORTS
    由于ClassA存在事务,所以跟REQUIRED的处理一样。
  4. NOT_SUPPORTED
    ClassB以非事务执行,但update的是同一条数据,也会获取锁超时,跟REQUIRES_NEW的处理一样。
  5. NEVER
    ClassA存在事务,因此ClassB抛出异常IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’,ClassA捕获后不影响ClassA的update,因此成功update年龄=11。
  6. MANDATORY
    同REQUIRED。
  7. NESTED
    到ClassB后,先update,再抛异常,由于本身是事务,所以会回滚。
    和REQUIRED不一样的地方是,虽然NESTED在子事务不会获取新的链接,但是会设置一个savepoint,即ClassB抛异常后,会回滚到ClassB未执行之前,此时抛出异常后,虽然会被ClassA捕获,但是能够成功提交,不会发生REQUIRED的异常,因此成功update年龄=11。

测试程序2

改一下程序1的ClassB,ClassB更新user2.年龄=12,即与ClassA更新2条不同的数据。
测试结果:

行为报错信息执行结果
REQUIREDorg.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only2个user.年龄均未被更新
REQUIRES_NEWjava.lang.RuntimeException: ClassB#updateUserAge RuntimeExceptionuser1.年龄被更新=11,user2.年龄未被更新(执行了ClassA.SQL,未执行ClassB.SQL)
SUPPORTS同REQUIRED同REQUIRED
NOT_SUPPORTED同REQUIRES_NEWuser1.年龄被更新=11,user2.年龄被更新=12(执行了ClassA和ClassB.SQL)
NEVERorg.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’同REQUIRES_NEW
MANDATORY同REQUIRED同REQUIRED
NESTEDorg.springframework.transaction.CannotCreateTransactionException: Could not create JDBC savepoint; nested exception is java.sql.SQLFeatureNotSupportedException: setSavepoint name同REQUIRES_NEW
  1. REQUIRED
    同程序1。
  2. REQUIRES_NEW
    由于2个Class不更新同条数据,所以未出现程序1的获取锁超时,后续流程同程序1。
  3. SUPPORTS
    同程序1。
  4. NOT_SUPPORTED
    不存在获取锁超时,且ClassB以非事务执行,因此即使抛出异常,也不影响update,ClassA捕获异常也不影响update,因此2个update均被执行。
  5. NEVER
    同程序1。
  6. MANDATORY
    同程序1。
  7. NESTED
    同程序1。

分析

通过2个测试程序,回到最开始的问题,项目里的那个方法不存在嵌套事务,因此指定为Propagation.REQUIRES_NEW其实是与Propagation.REQUIRED的效果一致,所以这里可删除掉Propagation.REQUIRES_NEW,避免歧义。
日常开发中,应该尽量避免嵌套事务,避免重复获取链接,获取新的链接本身会带来很多隐患:

  1. 浪费链接资源(挂起父事务后,无论当前支不支持事务,都需要重新获取链接,只是一个是在方法前在事务管理器里获取,而另一个则是方法执行到数据库操作时获取)。
  2. 获取锁超时,甚至导致死锁。

作者:曼特宁

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

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

相关文章

算法学习——LeetCode力扣图论篇3(127. 单词接龙、463. 岛屿的周长、684. 冗余连接、685. 冗余连接 II)

算法学习——LeetCode力扣图论篇3 127. 单词接龙 127. 单词接龙 - 力扣(LeetCode) 描述 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> … -> sk: 每一对相…

macOS Catalina for mac (macos 10.15系统)v10.15.7正式版

macOS Catalina是苹果公司专为麦金塔电脑推出的桌面操作系统,是macOS的第16个主要版本。它继承了苹果一贯的优雅与高效,不仅引入了分割视图和侧边栏,还带来了全新的音乐和播客应用,极大地提升了用户体验。在隐私保护和安全性方面&…

Oracle 数据库、实例、用户、表空间、表之间的关系(新手入门)

Oracle 数据库、实例、用户、表空间、表之间的关系 数据库: Oracle数据库是数据的物理存储。这就包括(数据文件ORA或者DBF、控制文件、联机日志、参数文件)。其实Oracle数据库的概念和其它数据库不一样,这里的数据库是一个操作系…

python中dropna()函数的作用举例说明

在Python中,dropna()是一个Pandas库中的函数,用于从数据框(DataFrame)中删除包含缺失值(NaN)的行或列。它用于数据清洗和预处理阶段,以便去除缺失值,使数据更加规整。 dropna()函数…

软件杯 深度学习YOLOv5车辆颜色识别检测 - python opencv

文章目录 1 前言2 实现效果3 CNN卷积神经网络4 Yolov56 数据集处理及模型训练5 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 **基于深度学习YOLOv5车辆颜色识别检测 ** 该项目较为新颖,适合作为竞赛课题方向&#xff0…

nginx的缓存和gzip

nginx的缓存 缓存的基本思想是利用客户端访问的时间局限性,将客户端访问过的内容做一个副本,在一定时间内存放到本地,当改数据下次被访问时,不必连接到后端服务器反复去查询数据,而是由本地保存的副本响应数据。 保存…

服务器固定IP(固定出口IP)去访问外部服务

背景 服务器上有多个IP,那么在服务器请求外部服务的时候,到底是使用哪个IP呢?如果要使用特定的IP去请求外部服务,该如何设置呢? 分析 遇到一个实际的场景: 我们产品和其他产品联调,我们的服务…

【A-010】基于SSH的宠物狗商城系统(含论文)

【A-010】基于SSH的宠物狗商城系统(含论文) 开发环境: Eclipse/MyEclipse、Tomcat8、Jdk1.8 数据库: MySQL 项目介绍: 在科学技术飞速发展的今天,互联网成为人们快速获取、发布和传递信息的重要渠道&am…

【Docker笔记01】【基础内容】

一、前言 本系列是根据 B 站 尚硅谷 Docker 视频 学习记录笔记。因为没有视频课件,部分内容摘自 https://www.yuque.com/tmfl/cloud/dketq0。 本系列仅为自身学习笔记记录使用,记录存在偏差,推荐阅读原视频内容或本文参考笔记。 二、Docker…

基于springboot实现房屋租赁系统项目【项目源码+论文说明】

基于springboot实现房屋租赁系统演示 摘要 社会的发展和科学技术的进步,互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。互联网具有便利性,速度快,效率高,成本…

QT-左框选项卡软件界面框架

QT-左框选项卡软件界面框架 一、演示效果二、关键程序三、下载链接 一、演示效果 二、关键程序 #include <QTextBrowser> #include <QLabel> #include <QPushButton> #include <QSpacerItem> #include <QToolButton> #include <QDebug> #i…

浮点数(小数)在计算机中如何用二进制存储?

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/137182814 出自【进步*于辰的博客】 注&#xff1a;为了阐述更加严谨&#xff0c;本篇文章中将使…

【练习】滑动窗口思想

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Java算法&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.长度最小的子数组 题目解析 讲解 代码实现 2.无…

微信小程序(黑马优购:登录)

1.点击结算进行条件判断 user.js //数据 state: () >({ // address: {} address: JSON.parse(uni.getStorageSync(address) || {}), token: }), my-settle.vue computed: { ...mapGetters(m_cart,[checkedCount,total,checkedGoodsAmount]), …

MySQL经验分享:Shell开发问题

背景 之前整理过Python连接使用MySQL的经验&#xff0c;链接如下&#xff1a; pymysql封装总结_pymysql封装类-CSDN博客 相比高级语言&#xff0c;Shell与MySQL开发使用相对会更麻烦一些&#xff1b;由于 shell是linux命令集的概称&#xff0c;是属于命令行的人机界面。Shel…

机器视觉学习(八)—— 阈值化

目录 一、阈值化 二、二值化和示例 2.1 二值化 2.2 示例代码 一、阈值化 OpenCV是一个开源的计算机视觉库&#xff0c;可以用于图像处理和计算机视觉任务。阈值化是图像处理中的一种常见操作&#xff0c;可以将图像的像素值分成两个或多个不同的类别&#xff0c;通常是黑色…

python统计分析——双样本均值比较

参考资料&#xff1a;python统计分析【托马斯】 1、配对样本t检验 在进行两组数据之间的比较时&#xff0c;有两种情况必须区分开。在第一种情况中&#xff0c;同一对象在不同时候的两个记录值进行相互比较。例如&#xff0c;用学生们进入初中时的身高和他们一年后的身高&…

wails 创建Go 项目

##wails是一个可以让go和web技术编写桌面应用#安装wails go install github.com/wailsapp/wails/v2/cmd/wailslatest#检查环境 wails doctor 创建项目wails init -n AuxiliaryTools -t vue-ts拉取go.mod的依赖项 go mod tidy进入 frontend 前端安装依赖项npm install /pnpm ins…

GitHub - 使用SSH进行连接(续)

文章目录 前言开发环境SSH源码获取SSH源码分析最后 前言 上篇文章中提出了存在一些默认密钥文件会被SSH自动添加的猜测&#xff0c;现在我们通过一些分析来验证这个猜测。 开发环境 MacOS: 14.3.1SSH: OpenSSH_9.4p1 SSH源码获取 该怎么验证这个猜测呢&#xff1f;有个方法…

QT初识(2)

QT初识&#xff08;2&#xff09; 创建好项目之后&#xff0c;多了些什么东西&#xff1f;main.cppwidget.hwidget.cppwidget.ui.pro项目工程文件 我们今天来继续了解QT。如果没看过上一次QT初识的小伙伴可以点击这里&#xff1a; https://blog.csdn.net/qq_67693066/article/d…