聊一聊Spring中的@Transactional注解【下】【注解失效场景】
前言
尽管 @Transactional
注解在 Spring 中提供了方便的事务管理功能,我们在使用过程中却常常面临其失效的问题。事务失效可能导致意想不到的数据状态和错误,影响应用的稳定性和可靠性。本文将探讨一些常见的 @Transactional
失效场景,包括异常处理不当、事务传播行为设置错误,以及在非 Spring 管理的类中使用注解等情况。通过深入分析这些场景,我们将揭示如何避免这些常见陷阱,以确保事务管理的有效性。
一、@Transactional失效场景
1、代理角度
1.1同类内部方法间调用
- 非@Tranactional注解方法调用@Transactional注解方法
@Transactional(rollbackFor = Exception.class)
public void createUser(UserInfo user) throws UnknownHostException {userDao.save(user);
}public void createUser2(UserInfo user) throws UnknownHostException {createUser(user);
}
当 createUser2
方法直接调用 createUser
方法时,Spring 的 AOP 机制无法拦截这个方法调用。createUser
上的@Tranactional失效。
IDE也会给出相应警告:
- @Tranactional注解方法调用@Transactional注解方法
@Transactional(rollbackFor = Exception.class)
public void createUser(UserInfo user) throws UnknownHostException {userDao.save(user);
}@Transactional(rollbackFor = Exception.class)
public void createUser2(UserInfo user) throws UnknownHostException {createUser(user);
}
当 createUser2
方法直接调用 createUser
方法时,Spring 的 AOP 机制无法拦截这个方法调用。createUser
上的@Tranactional失效。
createUser
中DML以非事务形式执行。
1.2如果@Transactional标注到接口上
注解标注到接口上,如果使用CGLIB对目标类进行代理,将无法解析到@Transactional
- 定义接口和实现类
public interface UserService {void createUser(UserInfo user);
}public class UserServiceImpl implements UserService {@Override@Transactional(rollbackFor = Exception.class) // 注解在接口实现上public void createUser(UserInfo user) {// DML 操作System.out.println("User created: " + user.getUserId());}
}
- 配置Spring和使用CGLIB代理
@Configuration
@EnableTransactionManagement
public class AppConfig {@Beanpublic UserService userService() {return new UserServiceImpl(); // CGLIB 代理}@Beanpublic DataSource dataSource() {// 配置数据源}
}
- 测试类
public class TransactionTest {@Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user = new UserInfo();user.setUserId(1);userService.createUser(user);}
}
在这个案例中,UserService
接口的 createUser
方法被 @Transactional
注解标记,而 UserServiceImpl
实现类也是正确实现了这个接口。
由于 Spring 在这里使用 CGLIB 代理(当代理目标类没有实现接口时,默认使用 CGLIB),它将创建一个 UserServiceImpl
的代理对象。
当通过接口调用 userService.createUser(user)
时,代理对象并不会解析接口上的 @Transactional
注解,导致事务管理失效。
1.3final、static修饰的类或者方法上使用注解
1.3.1 final 修饰的类
- 无法被代理:如果一个类被声明为
final
,Spring 无法对其进行子类化,因此无法生成代理。由于 Spring 的事务管理依赖于代理机制,@Transactional
注解将不会生效,相关的事务管理功能将被忽略。
1.3.2static 修饰的方法
- 无法代理:
static
方法属于类本身,而不是类的实例,因此 Spring 事务管理也无法对其进行代理。这意味着,即使在static
方法上使用@Transactional
,事务管理也不会生效。
1.4没有被Spring管理的bean
- Spring 的事务管理依赖于代理机制来处理方法调用。当你在一个类上使用
@Transactional
注解时,Spring 会创建一个代理对象,并在调用被注解的方法时应用事务逻辑。 - 如果一个 bean 没有被 Spring 管理(例如,它不是 Spring 上下文中的一个 bean),Spring 就不会为它创建代理,因此
@Transactional
注解将不起作用。
2、框架及底层角度
2.1非public修饰的方法
/*** 创建代理时,会把方法和事务属性放到缓存对象attributeCache中* 计算事务属性时,会判断方式是否public修饰*/// AbstractFallbackTransactionAttributeSource
private final Map<Object, TransactionAttribute> attributeCache = new ConcurrentHashMap<>(1024);protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {// 不允许非public修饰的方法if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}
}
2.2多线程
@Service
public class UserService {private final UserDao userDao;private final UserLoginService userLoginService;public UserService(UserDao userDao, UserLoginService userLoginService) {this.userDao = userDao;this.userLoginService = userLoginService;}@Transactional(rollbackFor = Exception.class)public void createUser(UserInfo user) {userDao.save(user);UserLoginInfo userLoginInfo = new UserLoginInfo();userLoginInfo.setId(user.getUserId());userLoginInfo.setUserId(user.getUserId());userLoginService.saveUserLoginInfo(userLoginInfo);}
}
@Service
public class UserLoginService {private final UserLoginDao userLoginDao;public UserLoginService(UserLoginDao userLoginDao) {this.userLoginDao = userLoginDao;}@Transactional(rollbackFor = Exception.class)@Asyncpublic void saveUserLoginInfo(UserLoginInfo userLoginInfo) {userLoginDao.save(userLoginInfo);}
}
理论上createUser和saveUserLoginInfo都有@Transactional注解时,两个方法中的DML处于同一个事务。
当在saveUserLoginInfo上加上@Async时,saveUserLoginInfo会被代理,新开一个线程执行。
由于事务时绑定在线程上的,这就意味着saveUserLoginInfo会新建一个事务。
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr != null) {if (logger.isTraceEnabled()) {logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");}txInfo.newTransactionStatus(status);} else {if (logger.isTraceEnabled()) {logger.trace("No need to create transaction for [" + joinpointIdentification +"]: This method is not transactional.");}}// 即使没有创建新的事务,也总是绑定事务信息到线程上txInfo.bindToThread();return txInfo;
}
2.3数据库本身不支持事务
MySQL如果使用MyISAM存储引擎,则不支持事务,自然@Transactional也没有效果
2.4未开启事务
在某些MVC项目中需要在配置文件中,手动配置事务相关参数
3、使用角度
3.1rollbackFor
属性设置错误而导致 @Transactional
注解失效的情况
- 定义用户服务类
@Service
public class UserService {@Autowiredprivate UserDao userDao;@Autowiredprivate UserLoginService userLoginService;@Transactional(rollbackFor = NullPointerException.class) // 错误设置public void createUser(UserInfo user) {userDao.save(user); // 可能会抛出 Exception// 假设下面这行会抛出一个 RuntimeExceptionUserLoginInfo userLoginInfo = null; // 这里故意设置为 nulluserLoginInfo.setUserId(user.getUserId()); // 这里会抛出 NullPointerExceptionuserLoginService.saveUserLoginInfo(userLoginInfo);}
}
- 测试类
public class TransactionTest {@Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user = new UserInfo();user.setUserId(1);userService.createUser(user);}
}
- 如果
userDao.save(user)
方法抛出一个非NullPointerException
类型的异常(例如SQLException
),则由于rollbackFor
属性不包含该异常类型,事务不会回滚。 - 即使后续代码中发生了
NullPointerException
,由于前面的操作已经导致事务处于失败状态,整个事务也不会被正确处理。
3.2异常被内部 catch
块捕获而导致 @Transactional
注解失效的情况
- 定义用户服务类
@Service
public class UserService {@Autowiredprivate UserDao userDao;@Autowiredprivate UserLoginService userLoginService;@Transactional(rollbackFor = Exception.class)public void createUser(UserInfo user) {try {userDao.save(user); // 假设这可能抛出 SQLException} catch (SQLException e) {// 捕获异常,事务不会回滚System.out.println("Caught SQLException: " + e.getMessage());// 不重新抛出异常,导致事务管理失效}// 下面的代码仍然会被执行UserLoginInfo userLoginInfo = new UserLoginInfo();userLoginInfo.setUserId(user.getUserId());userLoginService.saveUserLoginInfo(userLoginInfo);}
}
- 测试类
public class TransactionTest {@Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user = new UserInfo();user.setUserId(1);userService.createUser(user);}
}
-
当
userDao.save(user)
抛出SQLException
时,该异常被catch
块捕获并处理。 -
由于异常没有被重新抛出,
@Transactional
注解无法检测到异常的发生,因此事务不会回滚。 -
这意味着即使
userDao.save(user)
失败,后续的userLoginService.saveUserLoginInfo(userLoginInfo)
仍然会被执行,导致数据的不一致。
3.3事务传播行为不当而导致 @Transactional
注解失效的情况
- 定义用户服务类
@Service
public class UserService {@Autowiredprivate UserDao userDao;@Autowiredprivate UserLoginService userLoginService;@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void createUser(UserInfo user) {userDao.save(user); // 可能抛出异常// 调用另一个方法,假设这个方法没有@Transactional注解userLoginService.saveUserLoginInfo(user.getUserId());}
}
- 用户登录服务类
@Service
public class UserLoginService {@Autowiredprivate UserLoginDao userLoginDao;public void saveUserLoginInfo(Long userId) {// 假设这个方法执行一些 DML 操作// 但这里没有 @Transactional 注解userLoginDao.saveLoginInfo(userId);}
}
- 测试类
public class TransactionTest {@Autowiredprivate UserService userService;public void testCreateUser() {UserInfo user = new UserInfo();user.setUserId(1);userService.createUser(user);}
}
createUser
方法的传播行为被设置为 Propagation.REQUIRES_NEW
:
- 当
createUser
方法被调用时,会启动一个新的事务。 - 在该新事务中,如果
userDao.save(user)
抛出异常,整个事务将回滚。 - 然而,
userLoginService.saveUserLoginInfo(user.getUserId())
方法没有被@Transactional
注解包裹,且它是在createUser
方法的同一事务中调用的。
关键问题:
- 因为
saveUserLoginInfo
方法没有事务管理,所以即使createUser
方法中的事务因为异常回滚,saveUserLoginInfo
方法中的操作仍然会被提交。 - 这导致了数据的不一致性,因为在
createUser
方法失败时,saveUserLoginInfo
方法的 DML 操作仍然会被执行。
相关文章:

聊一聊Spring中的@Transactional注解【下】【注解失效场景】
前言 尽管 Transactional 注解在 Spring 中提供了方便的事务管理功能,我们在使用过程中却常常面临其失效的问题。事务失效可能导致意想不到的数据状态和错误,影响应用的稳定性和可靠性。本文将探讨一些常见的 Transactional 失效场景,包括异常…...
对称加密与非堆成加密
http通信有一些什么问题 窃听 - 对称加密传递密钥 - 非对称加密安全速度 - 非对称加密 对称加密中间人攻击 - 证书证书伪造 - 消息摘要摘要伪造 - 数字签名 可能被窃听 http本身不具备加密功能,http报文使用明文方式发送 还可能存在验证问题 无法确认发送到的…...

江协科技STM32学习- P28 USART串口数据包
🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝…...

Linux脚本循环(for、while、until)
文章目录 for基础风格for列表风格for与seq组合for与大括号for循环处理脚本参数组合命令while基础while数字累加utilcontinue for基础风格 这种风格最像高级程序中的for循环 #!/bin/bashfor(( i0; i<10; i )) doecho "第$i次for循环" donefor列表风格 #!/bin/ba…...
文件系统上云的挑战
优质博文:IT-BLOG-CN 一、挑战/注意事项 【1】因文件系统HDFS没有关联信息OrderId等,不能对存量数据进行有策略的同步,因此目前是将所有的文件同步至云服务器SIN; 【2】海外数据和国内数据上传到各自的文件服务器后,…...

【北京迅为】《STM32MP157开发板嵌入式开发指南》-第七十一章 制作Ubuntu文件系统
iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐…...

中间件漏洞总结
参考:Tomcat漏洞详解-CSDN博客 tomcat 历史漏洞 阿里云漏洞库 (aliyun.com) 弱口令和war远程部署漏洞 弱口令 Tomcat8.* 登录页面:/manager/html 弱口令:tomcat/tomcat 后台Getshell 登录到后台后可以通过部署 war 包进行 getshell wa…...
PySpark Yarn集群模式
目录 简介 一、PySpark简介 二、YARN模式概述 三、配置环境 1. 安装与配置Spark 2. 配置Hadoop和YARN 3. 启动yarn 四、编写PySpark脚本 五、提交PySpark作业到YARN 参数解释: 六、常见问题及解决 七、总结 简介 随着大数据的普及,Spark作为…...
Matlab基于经纬度点并行提取指定日期的tiff栅格位置的值
文章目录 前言一、基本说明二、代码 前言 该 MATLAB 代码用于从 GeoTIFF 文件中提取基于特定地理位置(经纬度)和日期的某个点的相关数据。代码首先读取一个包含事件数据(日期、经纬度)的 Excel 文件,然后根据日期和位…...
npm入门教程19:npm包管理
一、代码更新 遵循语义化版本控制: 在更新包时,应遵循语义化版本控制(Semantic Versioning,简称SemVer)规范。这意味着版本号的变更应反映代码变更的程度,通常遵循主版本号.次版本号.修订号的格式。主版本号…...

【NOIP提高组】虫食算
【NOIP提高组】虫食算 C语言C 💐The Begin💐点点关注,收藏不迷路💐 所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子: 43#98…...

软件测试面试题个人总结
前面看到了一些面试题,总感觉会用得到,但是看一遍又记不住,所以我把面试题都整合在一起,都是来自各路大佬的分享,为了方便以后自己需要的时候刷一刷,不用再到处找题,今天把自己整理的这些面试题…...

HTML 语法规范——代码注释、缩进与格式、标签与属性、字符编码等
文章目录 一、代码注释1.1 使用注释的主要目的1.2 使用建议二、标签的使用2.1 开始标签和结束标签2.2 自闭合标签2.3 标签的嵌套2.4 标签的有效性三、属性四、缩进与格式4.1 一致的缩进4.2 元素单独占用一行4.3 嵌套元素的缩进4.4 避免冗长的行五、字符编码六、小结在开发 HTML…...

【Wi-Fi】WiFi中QAM及16-QAM、64-QAM、512-QAM、1024-QAM、2048-QAM、4096-QAM整理
参考链接 什么是QAM?QAM是如何工作的? - 华为 不同阶QAM调制星座图中,符号能量的归一化计算原理 - 知乎 16 QAM modulation vs 64 QAM modulation vs 256 QAM modulation 512 QAM vs 1024 QAM vs 2048 QAM vs 4096 QAM modulation type…...

红黑树的平衡之舞:数据结构中的优雅艺术
文章目录 前言🚀一、红黑树的介绍1.1 红黑树的概念1.2 红黑树的特点1.3 红黑树的性质 🚀二、红黑树结点的定义🚀三、红黑树的框架🚀四、旋转操作🚀五、红黑树的插入操作5.1 uncle结点存在且为红5.2 uncle结点不存在或者…...

angular实现list列表和翻页效果
说明:angular实现list列表和翻页效果 上一页 当前页面 下一页 效果图: step1: E:\projectgood\ajnine\untitled4\src\app\car\car.component.css .example-form-fields {display: flex;align-items: flex-start; }mat-list-item{background: antiquew…...

闯关leetcode——3285. Find Indices of Stable Mountains
大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/find-indices-of-stable-mountains/description/ 内容 There are n mountains in a row, and each mountain has a height. You are given an integer array height where height[i] represents t…...

算法【Java】—— 动态规划之斐波那契数列模型
动态规划 动态规划的思路一共有五个步骤: 状态表示:由经验和题目要求得出,这个确实有点抽象,下面的题目会带大家慢慢感受状态标识状态转移方程初始化:避免越界访问 dp 表,所以在进行填表之前我们要预先填…...

idea连接docker并构建镜像
安装docker 安装docker idea连接docker 安装docker插件 设置docker连接 设置docker.exe 这个docker.exe是为了运行docker,可以通过安装docker desktop获取 docker desktop下载地址 右键图标找到文件位置 在同级的resource中 编写Dockerfile # 使用官方 Nginx…...

百度如何打造AI原生研发新范式?
👉点击即可下载《百度AI原生研发新范式实践》资料 2024年10月23-25日,2024 NJSD技术盛典暨第十届NJSD软件开发者大会、第八届IAS互联网架构大会在南京召开。本届大会邀请了工业界和学术界的专家,优秀的工程师和产品经理,以及其它行…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...