当前位置: 首页 > news >正文

聊一聊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也会给出相应警告:

image-20241104141055525

  • @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 注解失效的情况

  1. 定义用户服务类
@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);}
}
  1. 测试类
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 注解失效的情况

  1. 定义用户服务类
@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);}
}
  1. 测试类
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 注解失效的情况

  1. 定义用户服务类
@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());}
}
  1. 用户登录服务类
@Service
public class UserLoginService {@Autowiredprivate UserLoginDao userLoginDao;public void saveUserLoginInfo(Long userId) {// 假设这个方法执行一些 DML 操作// 但这里没有 @Transactional 注解userLoginDao.saveLoginInfo(userId);}
}
  1. 测试类
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 中提供了方便的事务管理功能&#xff0c;我们在使用过程中却常常面临其失效的问题。事务失效可能导致意想不到的数据状态和错误&#xff0c;影响应用的稳定性和可靠性。本文将探讨一些常见的 Transactional 失效场景&#xff0c;包括异常…...

对称加密与非堆成加密

http通信有一些什么问题 窃听 - 对称加密传递密钥 - 非对称加密安全速度 - 非对称加密 对称加密中间人攻击 - 证书证书伪造 - 消息摘要摘要伪造 - 数字签名 可能被窃听 http本身不具备加密功能&#xff0c;http报文使用明文方式发送 还可能存在验证问题 无法确认发送到的…...

江协科技STM32学习- P28 USART串口数据包

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…...

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…...

文件系统上云的挑战

优质博文&#xff1a;IT-BLOG-CN 一、挑战/注意事项 【1】因文件系统HDFS没有关联信息OrderId等&#xff0c;不能对存量数据进行有策略的同步&#xff0c;因此目前是将所有的文件同步至云服务器SIN&#xff1b; 【2】海外数据和国内数据上传到各自的文件服务器后&#xff0c;…...

【北京迅为】《STM32MP157开发板嵌入式开发指南》-第七十一章 制作Ubuntu文件系统

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…...

中间件漏洞总结

参考&#xff1a;Tomcat漏洞详解-CSDN博客 tomcat 历史漏洞 阿里云漏洞库 (aliyun.com) 弱口令和war远程部署漏洞 弱口令 Tomcat8.* 登录页面&#xff1a;/manager/html 弱口令&#xff1a;tomcat/tomcat 后台Getshell 登录到后台后可以通过部署 war 包进行 getshell wa…...

PySpark Yarn集群模式

目录 简介 一、PySpark简介 二、YARN模式概述 三、配置环境 1. 安装与配置Spark 2. 配置Hadoop和YARN 3. 启动yarn 四、编写PySpark脚本 五、提交PySpark作业到YARN 参数解释&#xff1a; 六、常见问题及解决 七、总结 简介 随着大数据的普及&#xff0c;Spark作为…...

Matlab基于经纬度点并行提取指定日期的tiff栅格位置的值

文章目录 前言一、基本说明二、代码 前言 该 MATLAB 代码用于从 GeoTIFF 文件中提取基于特定地理位置&#xff08;经纬度&#xff09;和日期的某个点的相关数据。代码首先读取一个包含事件数据&#xff08;日期、经纬度&#xff09;的 Excel 文件&#xff0c;然后根据日期和位…...

npm入门教程19:npm包管理

一、代码更新 遵循语义化版本控制&#xff1a; 在更新包时&#xff0c;应遵循语义化版本控制&#xff08;Semantic Versioning&#xff0c;简称SemVer&#xff09;规范。这意味着版本号的变更应反映代码变更的程度&#xff0c;通常遵循主版本号.次版本号.修订号的格式。主版本号…...

【NOIP提高组】虫食算

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

软件测试面试题个人总结

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

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&#xff1f;QAM是如何工作的&#xff1f; - 华为 不同阶QAM调制星座图中&#xff0c;符号能量的归一化计算原理 - 知乎 16 QAM modulation vs 64 QAM modulation vs 256 QAM modulation 512 QAM vs 1024 QAM vs 2048 QAM vs 4096 QAM modulation type…...

红黑树的平衡之舞:数据结构中的优雅艺术

文章目录 前言&#x1f680;一、红黑树的介绍1.1 红黑树的概念1.2 红黑树的特点1.3 红黑树的性质 &#x1f680;二、红黑树结点的定义&#x1f680;三、红黑树的框架&#x1f680;四、旋转操作&#x1f680;五、红黑树的插入操作5.1 uncle结点存在且为红5.2 uncle结点不存在或者…...

angular实现list列表和翻页效果

说明&#xff1a;angular实现list列表和翻页效果 上一页 当前页面 下一页 效果图&#xff1a; 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】—— 动态规划之斐波那契数列模型

动态规划 动态规划的思路一共有五个步骤&#xff1a; 状态表示&#xff1a;由经验和题目要求得出&#xff0c;这个确实有点抽象&#xff0c;下面的题目会带大家慢慢感受状态标识状态转移方程初始化&#xff1a;避免越界访问 dp 表&#xff0c;所以在进行填表之前我们要预先填…...

idea连接docker并构建镜像

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

百度如何打造AI原生研发新范式?

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

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...