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 {@Autowiredprivate 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{@Autowiredprivate 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 {@Autowiredprivate 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
相关文章:
Springboot事务控制中A方法调用B方法@Transactional生效与不生效情况实战总结
介绍 本篇对Springboot事务控制中A方法调用B方法Transactional生效与不生效情况进行实战总结,让容易忘记或者困扰初学者甚至老鸟的开发者,只需要看这一篇文章即可立马找到解决方案,这就是干货的价值。喜欢的朋友别忘记来个一键三连哈&#x…...
python -【三】循环语句
一、while 循环 while 语法 while 条件: 条件满足时,做事情 a 0 while a < 100:print(i like python ...)a 1求 1-100 的总和 i 1 sum 0 while i < 100:sum ii 1 print(f1-100 的和是 {sum})""" 1-100 的和是 5050 ""&…...
类的内存对齐位段位图布隆过滤器哈希切割一致性哈希
文章目录 一、类的内存对齐1.1规则1.2原因 二、位段2.1介绍2.2内存分配问题2.3跨平台问题2.4使用的注意事项 三、位图的应用3.1 给40亿个不重复的无符号整数,找给定的一个数。(int的范围可以到达42亿多)3.2 给定100亿个整数,设计算…...
于ThinkPHP开发的赛事报名小程序
基于ThinkPHP开发的赛事报名微信小程序 功能包括 1、参赛公告 2、会员中心(会员注册、登录、成绩查询、资料管理、参赛记录管理) 3、个人报名和企业报名 (身份证验证防止重复报名) 4、培训报名 5、查询是否在库人员,根…...
前端学习--React部分
文章目录 前端学习--React部分前言1.React简介1.1React的特点1.2引入文件1.3JSX🍉JSX简介与使用🍉JSX语法规则 1.4模块与组件🍉模块🍉组件 1.5安装开发者工具 2.React面向组件编程2.1创建组件🍉函数式组件🍉…...
24V_2A_1.2MHZ|PCD0303升压恒频LCD背光源专用电路超小体积封装
概述 PCD0303是一个恒定频率,6针SOT23电流模式升压转换器用于小型低功耗应用。PCD0303 以1.2MHz切换,并且允许使用微小的,低成本电容器和电感器2mm或更小,内部软启动会产生较小的涌入电流延长电池寿命。PCD0303具有自动切换至轻负载下的脉冲…...
python生成词云图
生成词云图的话需要先对数据进行分词处理 , 分词方法点击查看 import pandas as pd from collections import Counter from wordcloud import WordCloud import matplotlib.pyplot as plt# 假设您已经按照之前的步骤处理了数据,并且处理后的数据保存在comments_proc…...
【使用ChatGPT构建应用程序】应用程序开发概述:1. 管理秘钥、2. 数据安全、3. 与应用程序解耦、4. 注意提示语的注入攻击
文章目录 一. 首先注意的两个方面1. 管理API密钥1.1. 用户提供API密钥1.2. 你自己提供API密钥 2. 数据安全和数据隐私 二. 软件架构设计原则:与应用程序解耦三. 注意LLM提示语的注入攻击1. 分析输入和输出2. 监控和审计3. 其他要注意的注入情况 在了解了ChatGPT的文…...
【JavaScript脚本宇宙】不可或缺的Web开发工具:图表和可视化
图形化你的数据:六款顶级JavaScript库全接触 前言 在本文中,我们将深入探讨六个强大的JavaScript库,这些库被广泛应用于数据可视化和交互式图形展示。我们将了解每个库的概述、主要特性、使用示例以及使用场景,以帮助读者更全面…...
自然语言处理(NLP)中的迁移学习
Transfer Learning in NLP 迁移学习(Transfer Learning)无疑是目前深度学习中的新热点(相对而言)。在计算机视觉领域,它已经应用了一段时间,人们使用经过训练的模型从庞大的ImageNet数据集中学习特征&…...
PLC集成BL121PO网关优化智能电网的远程管理PLC转OPC UA协议
随着工业自动化技术的不断发展,智能电网等复杂系统对于设备之间高效通信的需求日益增加。PLC转OPC UA协议转换网关BL121PO作为一款领先的协议转换设备,通过其独特的设计和功能,为用户提供了高效、安全的PLC接入OPC UA的解决方案。 设备概述 …...
爬虫案例(读书网)
一.我们还是使用简单的bs4库和lxml,使用xpath: 导入下面的库: import requests from bs4 import BeautifulSoup from lxml import etree 我们可以看见它的div和每个书的div框架,这样会观察会快速提高我们的简单爬取能力。 二.实…...
Linux系统编程(五)多线程创建与退出
目录 一、基本知识点二、线程的编译三、 线程相关函数1. 线程的创建(1)整型的传入与接收(2)浮点数的传入与接收(3)字符串的传入与接收(4)结构体的传入与接收 2. 线程的退出3. 线程的…...
计算机毕业设计 | SpringBoot个人博客管理系统(附源码)
1,绪论 1.1 背景调研 在互联网飞速发展的今天,互联网已经成为人们快速获取、发布和传递信息的重要渠道,它在人们政治、经济、生活等各个方面发挥着重要的作用。互联网上发布信息主要是通过网站来实现的,获取信息也是要在互联网中…...
字母的大小写转换
自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中,字符串对象提供了lower()方法和upper()方法进行字母的大小写转换,即可用于将大写字母转换为小写字母或者将小写字…...
JTW结构
JTW(JSON Web Token)的结构 在这篇笔记中,我们将了解JTW(JSON Web Token)的结构。我们将看到JTW是如何创建的,令牌的各个部分是什么,以及您如何自己构建和构造JTW。您还将了解一些这种结构的含义,以及使用JTW进行授权时的一些结果优缺点。 基本上,JTW本质上就是一个…...
debian11安装留档@VirtualBox
因为debian12无法安装tpot,所以又把11重新安装一遍,以前的安装文档:安装Debian 11 留档-CSDN博客 下载光盘 华为云地址:https://repo.huaweicloud.com/debian-cd/11.0.0/amd64/iso-cd/ 使用了debian11 教育版,比较有…...
SpringBoot——整合Thymeleaf模板
目录 模板引擎 新建一个SpringBoot项目 pom.xml application.properties Book BookController bookList.html 编辑 项目总结 模板引擎 模板引擎是为了用户界面与业务数据分离而产生的,可以生成特定格式的页面在Java中,主要的模板引擎有JSP&…...
电商推荐系统+电影推荐系统【虚拟机镜像分享】
电商推荐系统电影推荐系统【虚拟机镜像分享】 所有组件部署好的镜像下载(在下面),仅供参考学习。(百度网盘,阿里云盘…) 博主通过学习尚硅谷电商推荐电影推荐项目,将部署好的虚拟机打包成ovf文…...
(函数)判断素数(C语言)
一、运行结果; 二、源代码; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明素数判断函数; void prime(int number);int main() {//初始化变量值;int number 0;//获取用户输入的数据;printf(&quo…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...
boost::filesystem::path文件路径使用详解和示例
boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类,封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解,包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...
起重机起升机构的安全装置有哪些?
起重机起升机构的安全装置是保障吊装作业安全的关键部件,主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理: 一、超载保护装置(核心安全装置) 1. 起重量限制器 功能:实时监测起升载荷&a…...
