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

Spring事务失效的常见陷阱与解决方案

本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。

原理

Spring事务的原理是:通过AOP切面的方式实现的,也就是通过代理模式去实现事务增强。

具体过程是:对包含@Transactional注解的方法进行拦截,然后重写,重新在方法里加入异常回滚的逻辑。而且,每个线程都是独立管理自己的事务,相互隔离。

原理简单,使用起来也简单,也就是在方法上打上@Transactional注解,然后事务就正常生效了。也很少有人去验证异常情况下是否能真正的回滚。

Spring事务让我熟悉的地方是哪哪看起来都简单,让我陌生的地方使用时的变种较多,有时候莫名其妙的不生效。

源码

以上原理的相关源码如下:

2024-10-10-enjoyvtu.png

2024-10-10-wvdvgykr.png

2024-10-10-gvtmfboa.png


实践出真知

有时候在编码过程中,我会发现某些场景下的事务会失效,总有一些意想不到的情况和隐藏的坑等着你去发现。 我认为验证事务的最佳方法就是:记住基本原则 + 动手实践。记住基本原则能帮助你快速解决常规问题,而动手实践则能验证那些不常见或不确定的问题。


几种事务不生效的用法

以下是几种常见的Spring事务失效的情况,读者们一定要牢记。这不仅对日常编码非常有帮助,还能在面试时展示你的知识。

2024-10-17-vbrmxqsf.webp

  • private方法

Spring是通过AOP代理的方式实现事务增强的,但是private方法无法被代理,所以在private方法上打@Transactional注解是不生效的。

  • final、static修饰的方法

和private方法类似,final和static修饰的方法也无法被代理,所以@Transactional注解也不生效。

因为,static是属于类方法,final修饰的方法无法被重写,自然也就无法植入事务增强代码。

  • Bean对象没有被Spring托管

某个类一定要被Spring托管,那才能通过@Transactional注解去增强事务。如果只有@Transactional注解,而没有把类交给Spring托管,事务也是不生效的。类似如下情况:

// 此处没有@Service注解,此类不被spring托管,及时有@Transactional也不生效
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic final void createAndUpdateUser() {createUser();updateUserById();}public void createUser() {User user = new User();user.setId(2L);user.setName("test2");user.setEmail("test2" + "@test.com");userMapper.insert(user);System.out.println("create user");}public void updateUserById() {User user = userMapper.findById(1L);user.setName("admin1");userMapper.update(user);int i = 1 / 0; // 此处会抛出异常System.out.println("update user");}
}
  • 异常被吞掉

如果在业务代码里,通过try…catch捕获了异常,同时又没有继续抛出异常时,Spring事务也是不生效的。

因为代理增强的逻辑就是要发现了异常,才能回滚事务。如果异常被方法本身吞掉了,则代理会认为没有异常,从而无法回滚。

  • 非RuntimeException异常

Spring事务默认会回滚RuntimeException 及其子类,以及 Error 类型的异常。如果是其余异常,则不会回滚。源码处可见:

2024-10-10-ktshsktq.png

这种非RuntimeException异常场景下,需要做2个动作从而保证事务回滚。

  1. 捕获异常,然后抛出自定义异常。

  2. 自行在@Transactional注解中增加@Transactional(rollbackFor = XxxxxxxException.class)属性。或者直接使用rollbackFor = Exception.class,也就免去了第一步。

  • 异步线程的场景

多个线程的场景下,只需要牢记每个线程只管理自己的事务即可。每个线程都有一个独立的事务上下文,存在ThreadLocal中,所以事务信息在不同线程之间是隔离的。

  • 重灾区:在同一个类中调用本类的方法

这个失效场景,是最容易出错的,而且变种还多。在同一个类中调用本类的方法时,牢记以下2点,即可破局:

  1. 是否会开启事务依赖此类的第一个被外部调用的方法。如果此类的第一个被外部调用的方法有@Transactional注解,那事务生效。

  2. 调用自己内部方法时,采用的是this.xxxMethod()的方式,这种方式是不会走AOP代理的,所以被调用的内部方法的@Transactional注解不生效。

如果确实需要调用内部方法,并且要事务生效的话,那只能将被调用的内部方法独立到新的类中,同时交给Spring管理。


一道面试题

以上关于事务不生效的用法都比较好记,只有在同一个类中调用本类的方法场景下存在多种变种。具体请看这道面试题。请问以下createAndUpdateUser方法的事务生效吗?

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic final void createAndUpdateUser() { //注意这里有final修饰createUser();updateUserById();}@Transactionalpublic void createUser() {User user = new User();user.setId(2L);user.setName("test2");user.setEmail("test2" + "@test.com");userMapper.insert(user);System.out.println("create user");}@Transactional(rollbackFor = Exception.class)public void updateUserById() {User user = userMapper.findById(1L);user.setName("admin1");userMapper.update(user);int i = 1 / 0; // 此处会抛出异常System.out.println("update user");}
}

如果按照重灾区:在同一个类中调用本类的方法里提到的2个原则,则事务全部生效。

如果按照final、static修饰的方法里提到的原则,则事务全部不生效。

那结果如何呢?结果是以上方法的事务全部生效。

为什么呢?这里在补充一个原则:final修饰的方法如果带上@Transactional注解,事务情况按照被调用的方法自身的事务托管情况而定。

因为以上代码中的createUser方法和updateUserById方法,都有@Transactional注解,所以都生效。

这种特殊情况也实在是让人瞠目,不过只需要牢记以上几种不生效的用法即可,谁没事儿写这种@Transactional + final的代码呢?除了面试会问…



文章转载自:

http://Xldg9W1e.bwznL.cn
http://o8OofMWp.bwznL.cn
http://Do4eU5iH.bwznL.cn
http://yotVDfaL.bwznL.cn
http://SWf5LYCt.bwznL.cn
http://yP0VqOUh.bwznL.cn
http://fRCRHdfZ.bwznL.cn
http://xZ8KVHvE.bwznL.cn
http://Zc9K7umU.bwznL.cn
http://ABtWxgJY.bwznL.cn
http://CShG5hQC.bwznL.cn
http://8XaHxk02.bwznL.cn
http://Joo9z3Yc.bwznL.cn
http://MlLZmKRb.bwznL.cn
http://FuutbwzF.bwznL.cn
http://lNw3QJD9.bwznL.cn
http://HPDWeQAa.bwznL.cn
http://3tsqepE6.bwznL.cn
http://Y19vnZiV.bwznL.cn
http://vwPVX39c.bwznL.cn
http://HhP6KpHe.bwznL.cn
http://jJdbangf.bwznL.cn
http://KBCKJPCF.bwznL.cn
http://3ZJtSupd.bwznL.cn
http://4B7f2DGU.bwznL.cn
http://mVmUpsIZ.bwznL.cn
http://BzFY4ZWz.bwznL.cn
http://pDAs8apM.bwznL.cn
http://VtnnaqFM.bwznL.cn
http://inuHZSEk.bwznL.cn
http://www.dtcms.com/a/372080.html

相关文章:

  • 现代C++:现代C++?
  • ZSet
  • Linux初级篇
  • MySQL集群高可用架构——组复制 (MGR)
  • MySQL Cluster核心优缺点
  • RestTemplate使用 | RestTemplate设置http连接池参数
  • 01OpenCV简介
  • 美股市场股票数据API对接文档
  • Coze源码分析-资源库-删除插件-前端源码-核心接口与工具
  • 【深度学习】重采样(Resampling)
  • http接口幂等性
  • 无重复字符的最长子串
  • 架构思维:架构师视角的 FullGC 治理
  • pytest(1):fixture从入门到精通
  • Logstash中http_poller插件的用法
  • 软考中级习题与解答——第三章_操作系统(1)
  • 基于Python的智能工程资料自动生成模型设计与实现
  • 硬件:传感器(DS18B20)
  • muduo库搭建客户端
  • smpp3.4 协议
  • 阿里云高可用生产环境网络架构实战:VPC规划与多可用区部署
  • 中国移动中兴云电脑W132D-RK3528-2+32G-刷机固件包(非原机制作)
  • 疯狂星期四文案网第63天运营日记
  • 【PCIe EP 设备入门学习专栏 -- 8.2 PCIe EP 寄存器配置空间介绍】
  • Android开发-按钮触控
  • RocketMQ分布式消息中间件的核心原理与应用
  • MySQL 之 InnoDB 存储架构解析
  • 【LeetCode - 每日1题】构造和为0的n个不同整数数组
  • 使用MobaXterm连接Ubuntu时connection refused解决方法
  • Windows 内存整理和优化工具 - Wise Memory Optimize