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

介绍

本篇对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

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

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

相关文章

抖音本地生活服务商入驻指南分享!

当前&#xff0c;各大平台的团购外卖业务持续火爆&#xff0c;并逐渐成为众多创业赛道中的大热门。其中&#xff0c;本地生活服务更是在短时间内杀出重围&#xff0c;成为创业者们的首选。 根据抖音生活服务近日发布的《2023年度数据报告》&#xff0c;2023年&#xff0c;抖音生…

微信小程序图片懒加载如何实现?

微信小程序开发时&#xff0c;对于有图片的列表在加载时&#xff0c;为了用户体验更好&#xff0c;必需要对图片做懒加载。 如下图所示&#xff0c;页面在打开时&#xff0c;图片会按需加载&#xff0c;这样用户体验没有那么生硬。 以下将介绍图片懒加载的步骤&#xff1a; 1.…

R18 NTN中的RACH-less HO

在看R18 38.300时,发现NTN场景 增加了如下黄色字体的内容,R18 NTN支持了RACH-less HO,索性就简单看了看。 NTN RACH less HO相关的描述主要在38.331,38.213和38.321中。38.300中的描述显示:网络侧会通过RRCReconfiguration消息将RACH-less HO相关的配置下发给UE, 其中会包…

Java语言ADR药物不良反应系统源码Java+IntelliJ+IDEA+MySQL一款先进的药物警戒系统

Java语言ADR药物不良反应系统源码JavaIntelliJIDEAMySQL一款先进的药物警戒系统源码 ADR药物不良反应监测系统是一个综合性的监测平台&#xff0c;旨在收集、报告、分析和评价药品在使用过程中可能出现的不良反应&#xff0c;以确保药品的安全性和有效性。 以下是对该系统的详细…

Java 面向对象编程(OOP)

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

【qt】一次性学会所有对话框

对话框 一.前言二.文件对话框1.选择一个文件2.选择多个文件3.选择目录4.保存文件 三.颜色对话框1.获取颜色 四.字体对话框1.获取字体 五.输入对话框1.输入文本2.输入整数3.输入小数4.输入条目 六.消息对话框1.问题框2.信息框3.警告框4.危机框5.关于框6.关于qt框七.总结 一.前言…

CSS学习笔记:动画——使用animation添加动画效果

过渡和动画 啥是过渡? 例如transition: all 0.5s; -> 拥有该属性的标签&#xff0c;在样式改变时&#xff0c;将在设定的时间内逐渐过渡到另一个样式 啥是动画&#xff1f; 和过渡有点类似&#xff0c;只不过常常用于实现多个状态间的变化过程&#xff0c;动画过程可控…

基于PHP+MySQL组合开发的720VR全景小程序源码系统 一键生成三维实景 前后端分离带网站的安装代码包以及搭建教程

系统概述 这款源码系统是专门为实现 720VR 全景展示而设计的。它结合了先进的技术和创新的理念&#xff0c;能够将真实场景以全景的形式呈现给用户&#xff0c;让用户仿佛身临其境。该系统采用 PHP 进行后端开发&#xff0c;MySQL 作为数据库管理系统&#xff0c;确保了系统的…

【JAVA |Object类重写实例】Cloneable 接口、Comparable接口、比较器

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; &#x1f388;丠丠64-CSDN博客&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起…

阿贝云免费虚拟主机及免费云服务器评测

阿贝云是一家提供免费虚拟主机和免费云服务器的公司&#xff0c;其服务质量备受用户好评。用户可以通过阿贝云的网站 https://www.abeiyun.com 进行申请并获得免费服务。首先&#xff0c;我们来看看阿贝云的免费虚拟主机服务。免费虚拟主机提供了足够的存储空间和带宽&#xff…

HackTheBox-Machines--Cronos

文章目录 0x01 信息收集0x02 命令注入漏洞0x03 权限提升 Cronos 测试过程 0x01 信息收集 1.端口扫描 发现 SSH&#xff08;22&#xff09;、DNS&#xff08;53&#xff09;、HTTP&#xff08;80&#xff09;端口 nmap -sC -sV 10.129.227.2112.53端口开启&#xff0c;进行DNS…

靶机Moonraker_1练习报告

Moonraker: 1靶机练习实践报告 一、安装靶机 靶机是.ova文件&#xff0c;需要用VirtualBox打开&#xff0c;但我习惯于使用VMWare,因此修改靶机文件&#xff0c;使其适用于VMWare打开。 解压ova文件&#xff0c;得到.ovf文件和.vmdk文件。 直接用VMWare打开.ovf文件即可。 …

【VTKExamples::Utilities】第四期 CameraModifiedEvent

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例CameraModifiedEvent,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. CameraModifi…

OpenMV的VisionBoard视觉识别开发板学习记录

此篇博客仅用于对VisionBoard的开发板的学习研究记录&#xff0c;没有教学内容。 一、资料来源 开发板资料链接 开发板环境搭建手册 开发板视频教程 板子的资料网站 openmv官方的网站 目录 一、资料来源二、针对 VisionBoard的目标识别和定位总结1. 目标识别功能1.1 物体检测…

react ant 表格实现 拖拽排序和多选

项目背景 : react ant 要实现 : 有多选功能(实现批量删除 , 也可以全选) 可以拖拽(可以复制 , 方便顶部的搜索功能) 要实现效果如下 1 这是最初的拖拽功能实现 , 不能复制表格里的内容 , 不符合要求 2 更改了ROW的内容 , 实现了可以复制表格内容 代码 //控制是否可以选中表格…

Oracle数据库操作问题汇总

一、简介 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性好、使用方便、功能强&…

数据结构--二叉树--顺序存储判断是否二叉搜索树(2022统考真题)

数据结构–二叉树–顺序存储判断是否二叉搜索树(2022统考真题) 题目描述&#xff1a; 思路 二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称BST&#xff09;是一种具有以下性质的二叉树&#xff1a; 对于树中的每个节点 N&#xff0c;它的左子树&#xff08;如果…

重学java 49 List接口

但逢良辰&#xff0c;顺颂时宜 —— 24.5.28 一、List接口 1.概述: 是collection接口的子接口 2.常见的实现类: ArrayList LinkedList Vector 二、List集合下的实现类 1.ArrayList集合的使用及源码分析 1.概述 ArrayList是List接口的实现类 2.特点 a.元素有序 —> 按照什么顺…

Oracle中rman的增量备份使用分享

继上次使用RMAN的全量备份和异机还原以后&#xff0c;开始研究一下增量备份和还原的方法。相比于全量RMAN的备份还原&#xff0c;增量的备份还原就相对简单。本实践教程直接上操作&#xff0c;还是回归到一个问题&#xff0c;就是关于两个数据库创建时候&#xff0c;必须保持or…

【职业教育培训机构小程序】教培机构“招生+教学”有效解决方案

教培机构“招生教学”有效解决方案在数字化转型的浪潮中&#xff0c;职业教育培训机构面临着提升教学效率、拓宽招生渠道、增强学员互动等多重挑战。小程序作为一种新兴的移动应用平台&#xff0c;为解决这些痛点提供了有效途径。 一、职业教育培训机构小程序的核心功能 &…