事务传播行为Propagation

目录

  • 背景
  • 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. 获取锁超时,甚至导致死锁。

作者:曼特宁

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

npm镜像源证书过期问题解决

title: npm镜像源证书过期 search: 2024-02-29 文章目录 Failed to check for updates 问题ERR_PNPM_NO_PKG_MANIFESTnpm缓存清除指令权限不足导致删除不了解决方案npm创建基础配资文件 Failed to check for updates 问题 错误描述如上 检查完 node,vue,npm 的版本后都没啥问…

使用hping3网络工具构造TCP/IP数据包和进行DDos攻击

1 概述 hping3是一个强大的命令行工具,用于生成、发送和解析TCP/IP协议的数据包。它是开源的网络安全工具,由Salvatore Sanfilippo开发,主要应用于网络审计、安全测试和故障排查等领域。hping3不仅可以作为普通的网络连通性检测工具&#xf…

壁纸小程序Vue3(首页布局)

1.创建一个公共目录common来存放css和images App.vue中引用 <style lang"scss"> /*每个页面公共css */ import common/style/common-style.scss; </style> 2.渲染轮播图 <template><view class"homeLayout"><vi…

苍穹外卖04 (新增内表的外键id获取,多表分页查询,多表批量删除,修改先查在改内表外键id用主表的,起售时包含了“停售”状态的外关联表)

1. 新增套餐 1 需求分析和设计 业务规则&#xff1a; 套餐名称唯一 套餐必须属于某个分类 套餐必须包含菜品 名称、分类、价格、图片为必填项 添加菜品窗口需要根据分类类型来展示菜品 新增的套餐默认为停售状态 2 代码实现 1 根据分类id查询菜品 DishControllerGetMa…

手机有线投屏到直播姬pc端教程

1 打开哔哩哔哩直播姬客户端并登录(按下图进行操作) 2 手机用usb数据线连接电脑(若跳出安装驱动的弹窗点击确定或允许),usb的连接方式为仅充电(手机差异要求为仅充电),不同品牌手机要求可能不一样,根据实际的来 3 在投屏过程中不要更改usb的连接方式(不然电脑会死机需要重启) …

SAP 学习笔记 - 系统移行业务 - Migration cockpit工具 - 移行Material(品目)

本章开始&#xff0c;来研究研究移行工具 Migration cockpit。 理论啥的先放一边&#xff0c;来先做一个简单的实例&#xff0c;以对 Migration cockpit 有个大概的印象。 这里就先做一个移行品目的例子。 1&#xff0c;LTMC 启动Migration cockpit工具 默认给我启动了 IE &a…

C++11入门手册第二节,学完直接上手Qt(共两节)

C++多线程 #include <thread>:C++多线程库 #include <mutex>:C++互斥量库 #include <future>:C++异步库 多线程介绍 线程的创建 void entry_1() { }以普通函数作为线程入口函数:void entry_2(int val) { }​std::thread my_thread_1(entry_1);std::thr…

【b站李炎恢】Vue.js Element UI 下 | 十天技能课堂 | 更新中... | 李炎恢

课程地址&#xff1a;【Vue.js Element UI | 十天技能课堂 | 更新中... | 李炎恢】 https://www.bilibili.com/video/BV1U54y127GB/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 备注&#xff1a;虽然标题声明还在更新中&#xff0c;但是看一些常用…

npm软件包管理器

npm软件包管理器 一.npm 使用步骤二.npm安装所有依赖三.npm全局软件包-nodemon pm 简介链接&#xff1a; 软件包管理器&#xff0c;用于下载和管理 Node.js 环境中的软件包 一.npm 使用步骤 1.初始化清单文件&#xff1a; npm init -y &#xff08;得到 package.json 文件&am…

Django DRF视图

文章目录 一、DRF类视图介绍APIViewGenericAPIView类ViewSet类ModelViewSet类重写方法 二、Request与ResponseRequestResponse 参考 一、DRF类视图介绍 在DRF框架中提供了众多的通用视图基类与扩展类&#xff0c;以简化视图的编写。 • View&#xff1a;Django默认的视图基类&…

RocketMQ(版本4.9.4)+RocketMQ_Dashbord环境搭建(生产者、消费者的前置环境搭建)

一、官方网站下载 RocketMQ源码包 https://rocketmq.apache.org/zh/docs/4.x/introduction/02quickstart 二、把rocketMQ上传到Linux环境下解压&#xff0c;编译&#xff0c;执行以下命令&#xff08;需要提前装jdk和maven并配置好环境变量&#xff09; unzip rocketmq-all-4…

计算机网络-从输入网址到访问网站的全过程

当我们在浏览器中输入一个网址并按下回车键时&#xff0c;会发生一系列复杂的过程&#xff0c;最终使我们能够看到网页的内容。以下是这个过程的详细步骤&#xff1a; 客户端&#xff1a;首先&#xff0c;用户在浏览器中键入网址&#xff0c;然后浏览器会根据这个网址生成一个H…

基于PHP的新闻管理系统(用户发布版)

有需要请加文章底部Q哦 可远程调试 基于PHP的新闻管理系统(用户发布版) 一 介绍 此新闻管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。本新闻管理系统采用用户发布新闻&#xff0c;管理员审核后展示模式。 技术栈&am…

区间预测 | Matlab实现带有置信区间的GRNN广义回归神经网络时间序列未来趋势预测

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 Matlab实现带有置信区间的GRNN广义回归神经网络时间序列未来趋势预测 带有置信区间的GRNN(广义回归神经网络)时间序列未来趋势预测结合了广义回归神经网络(GRNN)的预测能力和置信区间的统计度量,以提供对未来…

C刊级 | Matlab实现DBO-BiTCN-BiGRU-Attention蜣螂算法优化双向时间卷积双向门控循环单元融合注意力机制多变量回归预测

C刊级 | Matlab实现DBO-BiTCN-BiGRU-Attention蜣螂算法优化双向时间卷积双向门控循环单元融合注意力机制多变量回归预测 目录 C刊级 | Matlab实现DBO-BiTCN-BiGRU-Attention蜣螂算法优化双向时间卷积双向门控循环单元融合注意力机制多变量回归预测效果一览基本介绍模型描述程序…

DevSecOps安全工具链介绍

目录 一、概述 二、安全工具链在平台中的定位 2.1 概述 2.2 分层定位 2.2.1 不同阶段的安全工具 2.2.2 安全工具金字塔 2.3 安全流水线集成概览 2.3.1 概述 2.3.2 标准流水线集成安全工具链概览图 三、安全工具链分类 3.1 概述 3.2 威胁建模类 3.2.1 威胁建模的概念…

SQL | SQL 3小时快速入门学习笔记

【【数据库】SQL 3小时快速入门 #数据库教程 #SQL教程 #MySQL教程 #database#Python连接数据库】 课程链接 一、SQL 1.资料库管理系统&#xff08;Database Management System&#xff0c;简称DBMS&#xff09; 是整理资料的软件。其主要功能包括数据的存储、检索、更新和删除…

Matlab中的脚本和函数

Matlab中的脚本和函数 文章目录 Matlab中的脚本和函数脚本创建脚本代码注释函数创建函数局部函数嵌套函数私有函数匿名函数补充知识函数句柄测试环境:Win11 + Matlab R2021a 脚本 ​ Matlab脚本是最简单的程序文件类型。它们可用于自动执行一系列 Matlab 命令,如命令行重复执…

【opencv】教程代码 —features2D(5)旋转相机的基本全景拼接

基本全景拼接 panorama_stitching_rotating_camera.cpp 将第二张图像进行透视变换后与第一张图像拼接 #include <iostream> // 包含了一些用于输入输出的函数 #include <opencv2/core.hpp> // 包含了OpenCV核心库的一些常用类和函数 #include <opencv2/imgpro…

深度学习理论基础(二)神经网络基础篇

目录 一、基础知识点Ⅰ 参数部分Ⅱ 模型部分 二、普通神经网络模型搭建1. 准备数据集2. 划分数据集3. 搭建模型4. 训练网络5. 测试网络6. 保存与导入模型 神经网络通过学习大量样本的输入与输出特征之间的关系&#xff0c;以拟合出输入与输出之间的方程&#xff0c;学习完成后&…