Springboot事务控制中A方法调用B方法@Transactional生效与不生效情况实战总结

news2024/10/5 10:24:34

介绍

本篇对Springboot事务控制中A方法调用B方法@Transactional生效与不生效情况进行实战总结,让容易忘记或者困扰初学者甚至老鸟的开发者,只需要看这一篇文章即可立马找到解决方案,这就是干货的价值。喜欢的朋友别忘记来个一键三连哈:)

实战步骤

由于A方法调用B方法的情况较多,此处按照 一定的命名规则复现各种情况。
例如:

c代表class,c0代表不同类,c1代表同类
a代表a方法,a0代表a方法无事务注解,a1代表有
b代表b方法,b0代表b方法无事务注解,b1代表有
e代表抛异常,ea代表a方法中抛异常;eb代表b方法执行抛异常;

组合起来:c1a0b1ea 表示:同类中a调用b,b上有事务,a中抛异常

创建表

CREATE TABLE `tb_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

创建测试类

AbstractUserService执行事务操作

@Service
public class AbstractUserService {
    @Autowired
    private UserService userService;

    /**
     *  新增用户
     * @param username
     */
    public void saveUser(String username) {
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername(username);
        userService.save(userEntity);
    }

    /**
     *  更新密码
     * @param username
     */
    public void updatePassword(String username) {
        UserEntity entity = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername,username));
        entity.setPassword("123456");
        userService.updateById(entity);
    }

    /**
     * 制造异常
     */
    public void makeException() {
        int i=1/0;
    }
}

Service01 代表a类

@Service
public class Service01 extends AbstractUserService{
    @Autowired
    private Service02 service02;

    // 同类调用
    public void c1_a0_b1_ea(String username){
        saveUser(username);
        this.b1(username,false);
        makeException();
    }

    public void c1_a0_b1_eb(String username){
        saveUser(username);
        this.b1(username,true);
    }

    @Transactional(rollbackFor = Exception.class)
    public void c1_a1_b0_ea(String username){
        saveUser(username);
        this.b0(username,false);
        makeException();
    }


    @Transactional(rollbackFor = Exception.class)
    public void c1_a1_b0_eb(String username){
        saveUser(username);
        this.b0(username,true);
    }

    @Transactional(rollbackFor = Exception.class)
    public void c1_a1_b1_ea(String username){
        saveUser(username);
        this.b1(username,false);
        makeException();
    }

    @Transactional(rollbackFor = Exception.class)
    public void c1_a1_b1_eb(String username){
        try{
            saveUser(username);
            this.b1(username,true);
        }catch (Exception e){
            System.out.println("c1_a1_b1_eb执行失败:");
            throw new RuntimeException("c1_a1_b1_eb执行失败");
        }
    }

    // 不同类调用
    public void c0_a0_b1_ea(String username){
        saveUser(username);
        service02.b1(username,false);
        makeException();
    }

    public void c0_a0_b1_eb(String username){
        saveUser(username);
        service02.b1(username,true);
    }

    @Transactional(rollbackFor = Exception.class)
    public void c0_a1_b0_ea(String username){
        saveUser(username);
        service02.b0(username,false);
        makeException();
    }

    @Transactional(rollbackFor = Exception.class)
    public void c0_a1_b0_eb(String username){
        saveUser(username);
        service02.b0(username,true);
    }

    @Transactional(rollbackFor = Exception.class)
    public void c0_a1_b1_ea(String username){
        saveUser(username);
        service02.b1(username,false);
        makeException();
    }

    @Transactional(rollbackFor = Exception.class)
    public void c0_a1_b1_eb(String username){
        saveUser(username);
        service02.b1(username,true);
    }

    public void b0(String username,boolean hasException){
        updatePassword(username);
        if(hasException){
            makeException();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void b1(String username, boolean hasException){
        updatePassword(username);
        if(hasException){
            makeException();
        }
    }




}

Service02 代表b类

@Service
public class Service02 extends AbstractUserService{

    public void b0(String username,boolean hasException){
        updatePassword(username);
        if(hasException){
            makeException();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void b1(String username, boolean hasException){
        updatePassword(username);
        if(hasException){
            makeException();
        }
    }
}

测试接口TestController

@RestController
@RequestMapping("")
public class TestController {
    @Autowired
    private Service01 service01;

    /**
     * 同类
     * a没有事务,b有 ,异常发生在a中 不会回滚
     * @return
     */
    @GetMapping("/c1_a0_b1_ea")
    public String test1() {
        service01.c1_a0_b1_ea("c1_a0_b1_ea");
        return "ok";
    }

    /**
     * 同类
     *  a没有事务,b有 ,异常发生在b中 不会回滚
     * @return
     */
    @GetMapping("/c1_a0_b1_eb")
    public String test2() {
        service01.c1_a0_b1_eb("c1_a0_b1_eb");
        return "ok";
    }

    /**
     * 同类
     *  a有事务,b没有 ,异常发生在a中 会回滚
     * @return
     */
    @GetMapping("/c1_a1_b0_ea")
    public String test3() {
        service01.c1_a1_b0_ea("c1_a1_b0_ea");
        return "ok";
    }

    /**
     * 同类
     *  a有事务,b没有 ,异常发生在b中 会回滚
     * @return
     */
    @GetMapping("/c1_a1_b0_eb")
    public String test4() {
        service01.c1_a1_b0_eb("c1_a1_b0_eb");
        return "ok";
    }

    /**
     * 同类
     *  a有事务,b有 ,异常发生在a中 会回滚
     * @return
     */
    @GetMapping("/c1_a1_b1_ea")
    public String test5() {
        service01.c1_a1_b1_ea("c1_a1_b1_ea");
        return "ok";
    }

    /**
     * 同类
     *  a有事务,b有 ,异常发生在b中 会回滚
     * @return
     */
    @GetMapping("/c1_a1_b1_eb")
    public String test6() {
        service01.c1_a1_b1_eb("c1_a1_b1_eb");
        return "ok";
    }


    /**
     * 不同类
     *  a没有事务,b有 ,异常发生在a中 不会回滚
     * @return
     */
    @GetMapping("/c0_a0_b1_ea")
    public String test7() {
        service01.c0_a0_b1_ea("c0_a0_b1_ea");
        return "ok";
    }

    /**
     * 不同类
     *  a没有事务,b有 ,异常发生在b中 只有b回滚
     * @return
     */
    @GetMapping("/c0_a0_b1_eb")
    public String test8() {
        service01.c0_a0_b1_eb("c0_a0_b1_eb");
        return "ok";
    }

    /**
     * 不同类
     *  a有事务,b没有 ,异常发生在a中 会回滚
     * @return
     */
    @GetMapping("/c0_a1_b0_ea")
    public String test9() {
        service01.c0_a1_b0_ea("c0_a1_b0_ea");
        return "ok";
    }

    /**
     * 不同类
     *  a有事务,b没有 ,异常发生在b中 会回滚
     * @return
     */
    @GetMapping("/c0_a1_b0_eb")
    public String test10() {
        service01.c0_a1_b0_eb("c0_a1_b0_eb");
        return "ok";
    }

    /**
     * 不同类
     *  a有事务,b有 ,异常发生在a中 会回滚
     * @return
     */
    @GetMapping("/c0_a1_b1_ea")
    public String test11() {
        service01.c0_a1_b1_ea("c0_a1_b1_ea");
        return "ok";
    }

    /**
     * 不同类
     *  a有事务,b有 ,异常发生在b中 会回滚
     * @return
     */
    @GetMapping("/c0_a1_b1_eb")
    public String test12() {
        service01.c0_a1_b1_eb("c0_a1_b1_eb");
        return "ok";
    }
}

测试结果

http://localhost:9000/test/c0_a1_b1_eb

在浏览器中依次访问测试接口中的每个方法,得到表中结果:
以下情况未回滚或者半回滚,不在表里的均正常回滚事务。
在这里插入图片描述

原理总结

原理:
spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。
此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。

那回到一开始的问题,我们调用的方法A不带注解,因此代理类不开事务,而是直接调用目标对象的方法。当进入目标对象的方法后,执行的上下文已经变成目标对象本身了,因为目标对象的代码是我们自己写的,和事务没有半毛钱关系,此时你再调用带注解的方法,照样没有事务,只是一个普通的方法调用而已。
简单来说,内部调用本类方法,不会再走代理了,所以B的事务不起作用。

如果AB不同类,A调用的事代理类B,故B有事务。

参考文章

  • https://blog.csdn.net/weixin_36586564/article/details/105687331
  • https://juejin.cn/post/7031446300142862373
  • 【@Transactional注解失效的几种情况】
    https://blog.csdn.net/Yaml4/article/details/138123693

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

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

相关文章

如何简化不同网间文件摆渡的操作流程,降低IT人员工作量?

为了保护内部核心数据不被泄露&#xff0c;同时有效屏蔽外部网络攻击的风险&#xff0c;企业大多会选择实施网络隔离。将“自己人”与“外人”隔离&#xff0c;具有较强的安全敏感性。有些企业还会在内部网络中进一步划分&#xff0c;比如划分为研发网、办公网、生产网等&#…

【基于 PyTorch 的 Python 深度学习】9 目标检测与语义分割(2)

前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了优化候选框的几种方法。 一、优化候选框的…

Java 面向对象编程(OOP)

面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是Java编程语言的核心思想之一。通过OOP&#xff0c;Java提供了一种结构化的编程方式&#xff0c;使代码更易于维护和扩展。 一、类和对象 1. 类的定义 类是对象的蓝图或模板&#xff0c;定…

回收站删掉的照片还能找回来吗?掌握这5个方法,很简单!

“我一不小心在回收站中误删了一些照片&#xff0c;这些照片对我来说是比较重要的&#xff0c;不知道这些还有机会将它们找回来吗&#xff1f;” 当我们不小心将珍贵的照片从计算机的回收站中误删时&#xff0c;那种瞬间的心痛和焦虑难以言表。这些照片可能记录了我们生活中的重…

大数据开发面试题【Kafka篇】

83、介绍下Kafka&#xff0c;Kafka的作用?Kafka的组件?适用场景? kafka是一个高吞吐量、可扩展的分布式消息传递系统&#xff0c;在处理实时流式数据&#xff0c;并能够保证持久性和容错性 可用于数据管道、流分析和数据继承和关键任务应用&#xff08;发布/订阅模式&#…

雷达基数据绘制成雷达图

x波段雷达基数据绘制成雷达图 1.雷达基数据格式Z_RADR_I_ZR001_20240521020002_O_DOR_YLD2-D_CAP_FMT.bin.bz2 2.基数据读取 python f StandardData(i) # 新版本标准数据radarTime f.scantime # 获取雷达时次date_str radarTime.strftime(%Y-%m-%d %H:%M:%S)date_str d…

盘点10大灵动惊艳小演员❗谁是你的心头好?

盘点国内影视那些惊艳观众的小演员们无疑为影视作品注入了新的活力。以下是10个备受赞誉的小演员&#xff1a; 1.韩昊霖&#xff1a;凭借在《我和我的祖国》和《庆余年》中的出色表现 韩昊霖的演技赢得了观众和业界的广泛认可 他能够准确地把握角色的情感和细节&#xff0c;展…

STM32_USART

1、USART简介 USART&#xff0c;即Universal Synchronous/Asynchronous Receiver/Transmitter&#xff0c;通用同步/异步收发器。USART是STM32内部集成的硬件外设&#xff0c;可根据数据寄存器的一个字节数据自动生成数据帧时序&#xff0c;从TX引脚发送出去&#xff0c;也可自…

claude3国内API接口对接

众所周知&#xff0c;由于地理位置原因&#xff0c;Claude3不对国内开放&#xff0c;而国内的镜像网站使用又贵的离谱&#xff01; 因此&#xff0c;团队萌生了一个想法&#xff1a;为什么不创建一个一站式的平台&#xff0c;让用户能够通过单一的接口与多个模型交流呢&#x…

点云工具CloudCompare下载、安装与汉化

一、下载 软件免费开源&#xff0c;所以可以直接在官网下载&#xff0c;官网地址&#xff1a; CloudCompare - Open Source project 进入官网后&#xff0c;点击菜单栏【Download】 选择合适系统进行下载 二、安装 常规软件安装流程即可 三、汉化 打开软件后&#xff0c;…

社交媒体数据恢复:微信电话本

首先&#xff0c;请确保您的微信已登录&#xff0c;并且您具有管理员权限。接下来&#xff0c;按照以下步骤进行操作&#xff1a; 第一步&#xff1a;备份微信数据 在进行数据恢复之前&#xff0c;建议您先备份微信数据。这可以帮助您在数据丢失的情况下更好地保护您的微信数据…

成都百洲文化传媒有限公司引领电商服务新风尚

在当今数字化时代&#xff0c;电商行业正以前所未有的速度蓬勃发展&#xff0c;而在这个充满机遇与挑战的领域中&#xff0c;成都百洲文化传媒有限公司以其专业的电商服务&#xff0c;成为了行业的佼佼者。作为一家专注于电商服务的传媒公司&#xff0c;百洲文化不仅为商家提供…

NDK下载与配置以及遇到的问题

通过 Android Studio进行下载或者官网下载&#xff0c;下面是在 androidStudio中下载在项目中配置ndk 菜单栏点开 File-》Project Structure&#xff0c;可以看到配置好的ndk配置ndk的系统环境变量&#xff0c;系统变量 -> 新建一个变量名为 NDK_HOME -> 变量值为文件路…

新旅程:类与对象的魔法课堂

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…

AI大模型在测试中的深度应用与实践案例

文章目录 1. 示例项目背景2. 环境准备3. 代码实现3.1. 自动生成测试用例3.2. 自动化测试脚本3.3. 性能测试3.4. 结果分析 4. 进一步深入4.1. 集成CI/CD管道4.1.1 Jenkins示例 4.2. 详细的负载测试和性能监控4.2.1 Locust示例 4.3. 测试结果分析与报告 5. 进一步集成和优化5.1. …

RK3588 camera驱动总结二之图像格式

camera驱动中很重要的一个设置就是格式&#xff0c;此文来看看这块。 在驱动中有个重要的参数mbus-code&#xff0c;即Media Bus Pixel Codes&#xff0c;它描述的是用于在物理总线上传输的格式&#xff0c;比如 sensor 通过mipi dphy 向 isp 传输的图像格式&#xff0c;或者在…

AI预测体彩排3采取888=3策略+和值012路一缩定乾坤测试5月28日预测第4弹

今天继续基于8883的大底进行测试&#xff0c;今天继续测试&#xff0c;好了&#xff0c;直接上结果吧~ 首先&#xff0c;888定位如下&#xff1a; 百位&#xff1a;3,4,2,5,0,7,8,9 十位&#xff1a;3,2,4,1,6,7,8,9 个位&#xff1a;0,1,2,3,4,5,6,7 …

如果你使用vscode的ssh插件远程连接不了服务器,解决办法如下

CtrlShiftP打开命令面板&#xff0c;在命令面板中输入setting&#xff0c;选择User Settings 在页面中配置"remote.SSH.useLocalServer": true,保存重启后再重新连接就好了 再次连接就可以输入密码连接了

YOLOv5改进 | 卷积模块 | 提高网络的灵活性和表征能力的动态卷积【附代码+小白可上手】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 轻量级卷积神经网络由于其低计算预算限制了CNNs的深度&#xff08;卷积层数&#xff09;和宽度&#xff08;通道数&#xff09;&#xff0c;…

手机号码携号转网查询保障用户权益、信息透明、优化用户体验

携号转网服务是指在同一本地网范围内&#xff0c;蜂窝移动通信用户&#xff08;不含物联网用户&#xff09;变更签约的基础电信业务经营者而用户号码保持不变的一项服务。近年来&#xff0c;随着通信行业的不断发展&#xff0c;携号转网服务已成为满足用户个性化需求、提升服务…