美团一面:@Transactional 原理和常见的坑?

SpringBoot~分析总结为何我的@Transactional 注解的方法没有被Spring Transaction Manager管理起来 也就是说我们通过AOP切面是给代理类配置了事务管理机制, 并没有给被代理类配置事务管理机制, 我们知道通过代理类执行的方法最后还是要执行被代理类中的方法, 问题就出现在这,我们执行一个没有配置事务的代理类方法的时候,最后实在被代理类中执行了另一个方法, 而另一个方法在代理类中配置了事务管理机制, 但是我执行的是被代理类中的方法, 所以就不涉及到事务。@Transactional的实现原理是在业务方法外边通过Spring AOP包上一层事务管理器的代码(即插入切面, 切面上有事务的通知) 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:楼仔

5698204cc1e8b27684c6f8ab14a63c90.jpeg


Java 后端面试的时候,面试官经常会问到 @Transactional 的原理,以及容易踩的坑,今天就带大家把这几块知识吃透。

这篇文章,会先讲述 @Transactional 的 4 种不生效的 Case,然后再通过源码解读,分析 @Transactional 的执行原理,以及部分 Case 不生效的真正原因。

6fc8f2a8da3d50d2c82b79ab34f86464.png

项目准备

下面是 DB 数据和 DB 操作接口:

uidunameusex
1张三女
2陈恒男
3楼仔男
// 提供的接口
public interface UserDao {
    // select * from user_test where uid = "#{uid}"
    public MyUser selectUserById(Integer uid);
    // update user_test set uname =#{uname},usex = #{usex} where uid = #{uid}
    public int updateUser(MyUser user);
}

基础测试代码,testSuccess() 是事务生效的情况:

@Service
public class UserController {
    @Autowired
    private UserDao userDao;

    public void update(Integer id) {
        MyUser user = new MyUser();
        user.setUid(id);
        user.setUname("张三-testing");
        user.setUsex("女");
        userDao.updateUser(user);
    }

    public MyUser query(Integer id) {
        MyUser user = userDao.selectUserById(id);
        return user;
    }

    // 正常情况
    @Transactional(rollbackFor = Exception.class)
    public void testSuccess() throws Exception {
        Integer id = 1;
        MyUser user = query(id);
        System.out.println("原记录:" + user);
        update(id);
        throw new Exception("事务生效");
    }
}

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

事务不生效的几种 Case

主要讲解 4 种事务不生效的 Case:

  • 类内部访问 :A 类的 a1 方法没有标注 @Transactional,a2 方法标注 @Transactional,在 a1 里面调用 a2;

  • 私有方法 :将 @Transactional 注解标注在非 public 方法上;

  • 异常不匹配 :@Transactional 未设置 rollbackFor 属性,方法返回 Exception 等异常;

  • 多线程 :主线程和子线程的调用,线程抛出异常。

Case 1: 类内部访问

我们在类 UserController 中新增一个方法 testInteralCall():

public void testInteralCall() throws Exception {
    testSuccess();
    throw new Exception("事务不生效:类内部访问");
}

这里 testInteralCall() 没有标注 @Transactional,我们再看一下测试用例:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testSuccess();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的记录:" + user);
    }
}
// 输出:
// 原记录:MyUser(uid=1, uname=张三, usex=女)
// 修改后的记录:MyUser(uid=1, uname=张三-testing, usex=女)

从上面的输出可以看到,事务并没有回滚,这个是什么原因呢?

因为 @Transactional 的工作机制是基于 AOP 实现,AOP 是使用动态代理实现的,如果通过代理直接调用 testSuccess(),通过 AOP 会前后进行增强,增强的逻辑其实就是在 testSuccess() 的前后分别加上开启、提交事务的逻辑,后面的源码会进行剖析。

现在是通过 testInteralCall() 去调用 testSuccess(),testSuccess() 前后不会进行任何增强操作,也就是类内部调用,不会通过代理方式访问。

如果还是不太清楚,推荐再看看这篇文章,里面有完整示例,非常完美诠释“类内部访问”不能前后增强的原因:https://blog.youkuaiyun.com/Ahuuua/article/details/123877835

Case 2: 私有方法

在私有方法上,添加 @Transactional 注解也不会生效:

@Transactional(rollbackFor = Exception.class)
private void testPirvateMethod() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}

直接使用时,下面这种场景不太容易出现,因为 IDEA 会有提醒,文案为: Methods annotated with '@Transactional' must be overridable,至于深层次的原理,源码部分会给你解读。

Case 3: 异常不匹配

这里的 @Transactional 没有设置 rollbackFor = Exception.class 属性:

@Transactional
public void testExceptionNotMatch() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("事务不生效:异常不匹配");
}
测试方法:同 Case1

// 输出:
// 原记录:User[uid=1,uname=张三,usex=女]
// 修改后的记录:User[uid=1,uname=张三-test,usex=女]

@Transactional 注解默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚,至于深层次的原理,源码部分会给你解读。

Case 4: 多线程

下面给出两个不同的姿势,一个是子线程抛异常,主线程 ok;一个是子线程 ok,主线程抛异常。

父线程抛出异常

父线程抛出异常,子线程不抛出异常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
    throw new Exception("测试事务不生效");
}

父线程抛出线程,事务回滚,因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚,

子线程抛出异常

父线程不抛出异常,子线程抛出异常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务不生效");
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
}

由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

源码解读

下面我们从源码的角度,对 @Transactional 的执行机制和事务不生效的原因进行解读。

@Transactional 执行机制

我们只看最核心的逻辑,代码中的 interceptorOrInterceptionAdvice 就是 TransactionInterceptor 的实例,入参是 this 对象。

红色方框有一段注释,大致翻译为 “它是一个拦截器,所以我们只需调用即可:在构造此对象之前,将静态地计算切入点。”

fdac0634b7c33fee6801b433a5fee294.png

this 是 ReflectiveMethodInvocation 对象,成员对象包含 UserController 类、testSuccess() 方法、入参和代理对象等。

e7f42dbcf4af6f615bd01b5dd8801006.png

进入 invoke() 方法后:

e4c708f31a5f213aa0a5ea172cff720b.png

前方高能!!!这里就是事务的核心逻辑,包括判断事务是否开启、目标方法执行、事务回滚、事务提交。

e1e5d0a703cc491f1ec4a621f0c9f2c7.png

private 导致事务不生效原因

在上面这幅图中,第一个红框区域调用了方法 getTransactionAttribute(),主要是为了获取 txAttr 变量,它是用于读取 @Transactional 的配置,如果这个 txAttr = null,后面就不会走事务逻辑,我们看一下这个变量的含义:

e3d28b5490863954d1bc3120a47a3fa9.png

我们直接进入 getTransactionAttribute(),重点关注获取事务配置的方法。

6c3f8b7c2c19cff0b28f9978203dc980.png

前方高能!!!这里就是 private 导致事务不生效的原因所在 ,allowPublicMethodsOnly() 一直返回 false,所以重点只关注 isPublic() 方法。

701b34ecdbb9962c3b8d699d4afaf217.png

下面通过位与计算,判断是否为 Public,对应的几类修饰符如下:

  • PUBLIC: 1

  • PRIVATE: 2

  • PROTECTED: 4

eff849e7e8d1cf4af196aee9e63eac17.png

看到这里,是不是豁然开朗了,有没有觉得很有意思呢~~

异常不匹配原因

我们继续回到事务的核心逻辑,因为主方法抛出 Exception() 异常,进入事务回滚的逻辑:

7006a6a1f4ba1249a5f5093c84014a38.png

进入 rollbackOn() 方法,判断该异常是否能进行回滚,这个需要判断主方法抛出的 Exception() 异常,是否在 @Transactional 的配置中:

afbb37bd16942e3f67d49e1e21e6d5e9.png

我们进入 getDepth() 看一下异常规则匹配逻辑,因为我们对 @Transactional 配置了 rollbackFor = Exception.class,所以能匹配成功:

1ac56283b925c6d4b068258de7374490.png

示例中的 winner 不为 null,所以会跳过下面的环节。但是当 winner = null 时,也就是没有设置 rollbackFor 属性时,会走默认的异常捕获方式。

d1bb220e2130ff3854820f4b8c4e928e.png

前方高能!!!这里就是异常不匹配原因的原因所在 ,我们看一下默认的异常捕获方式:

4cce2fccf235ee631064e1f6d8f72abf.png

是不是豁然开朗,当没有设置 rollbackFor 属性时,默认只对 RuntimeException 和 Error 的异常执行回滚。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

69347b85f8bf9fe1a8eafb55d93b1cf4.png

已在知识星球更新源码解析如下:

99d1d581ffd8f3bd4fb4435001021ebe.jpeg

60d4f1b48c9008cd38a059b70ac9cc8a.jpeg

0a946ca0044c85c888edb7ec61b10175.jpeg

bc99e920376da4b86eb1776e2e550632.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
APP抓包-代理转发绕过反代理+Xposed绕过证书校验 而这里的牛牛不止设置了反代理还设置了证书校验,一闪而过的三个数据包就是在校验证书。而proxifier工具的转发机制获取的是网络接口出口数据,相当于是在网络接口设代理。这个时候就可以在夜神打开牛牛开始抓包了,打开牛牛可以看到proxifier有夜神流量通过。关闭代理之后牛牛就正常了,可恶的牛牛啊,这样我没办法抓包啊,怎么办啊。模拟器开启代理,burp开启监听。打开牛牛app,牛牛不在报错。牛牛app检测的是系统代理,也就是在模拟器或手机上设置的代理。再在浏览器下载两个模块,在夜神安装,用来屏蔽证书校验。 阅读详情

相关推荐

CANoe信号波形分析保姆级教程:Graphic窗口从配置到实战避坑

本文详细解析了CANoe的Graphic窗口在信号波形分析中的高效配置与实战应用技巧。从信号显示模式选择、测量光标进阶操作到信号分组策略,提供工程化解决方案,帮助工程师快速定位总线信号异常,提升汽车电子开发与测试效率。

weixin_29279047的博客 270

Java 权限修饰符 public /protected /default /private 区别

public 具有最大的访问权限,可以访问任何一个在classpath下的类、接口、异常等。它往往用于对外的情况,也就是对象或类对外的一种接口的形式。 protected 主要的作用就是用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西。 default 有时候也称为friendly,它是针对本包访问而设计的,任何处于本包下的类、接口、异...

Simple 专栏 979

自动驾驶仿真工具链集成:ECU TEST与VTD、VERISTAND的深度配置指南

本文详细介绍了自动驾驶仿真测试中ECU TEST、VTD与VERISTAND三大核心工具的深度集成与连接配置方法。通过解析工具链的角色分工,提供从单机到分布式部署的实战指南,并重点阐述了Test Bench Configuration与Test Configuration的关键参数设置、SSH免密登录配置,以及在线与离线工作模式的切换技巧,旨在帮助测试工程师高效构建稳定、自动化的硬件在环测试环境。

wind6的博客 791

spring 事务@Transantional 失效及解决方案和总结

7、事务失效,被cache吃掉了,cache 抛出了exception异常,@Transactional(rollbackFor = RuntimeException.class) 指定回滚RuntimeException。普通方法调用,即使B方法添加了@Transactional注解,此时A方法没有事务。2、线程中方法,事务会失效。利用代理对象来调用B方法,所以会回滚事务。A方法调用B的private普通方法。3、事务正常回滚,A方法调用B的普通方法。1、线程中方法,事务会失效。

qwer1154928613的博客 2992

CarSim转向控制对比指南:开环扭矩控制vs转角控制(附Simulink连接示例)

本文深入对比了CarSim中车轮转角控制与开环扭矩控制的核心原理与适用场景。开环扭矩控制能激活完整的转向系统动力学,更适用于EPS控制策略开发与驾驶员在环仿真等对真实性要求高的场景。文章提供了详细的Simulink接口配置步骤与连接示例,为车辆动力学仿真工程师提供了清晰的决策与实操指南。

weixin_29199873的博客 480

Zephyr RTOS 嵌入式 C 编程 (使用嵌入式 RTOS POSIX API)8. Zephyr RTOS 与以太网、Wi-Fi 及 TCP/IP

支持的 BSD 函数包括:socket()、close()、recv()、recvfrom()、send()、sendto()、connect()、bind()、listen()、accept()、fcntl()(用于设置非阻塞模式)、getsockopt()、setsockopt()、poll()、select()、getaddrinfo()和 getnameinfo()。向开发板烧录代码也可能存在问题。可以通过调用 net_if_up() 开启网络接口 ,调用 net_if_down() 关闭接口。

1344

@Transactional事务不生效的情况小结

@Transactional事务不生效的情况小结

皮球 4648

HarmonyOS 鸿蒙开发DevEco Studio OpenHarmony:安全测试

通过对HarmonyOS应用生命周期建模和应用攻击面建模,采用静态数据流分析技术,提高漏洞发现的准确率,同时覆盖20余种攻击面,65+漏洞测试项,帮助开发者提前发现和识别漏洞隐患。在检测报告中,会针对每一项漏洞风险项给出明确的修复建议,可以帮助开发者快速修复漏洞。

斌擎科技 1183

@Transactional失效情况

Spring事务管理方式 编码式事务管理:将事务控制代码编写在业务代码之中。 声明式事务管理:基于AOP(面向切面编程),事务管理与业务逻辑解耦。 声明式事务管理的两种实现:(1)在配置文件(xml)中配置。(2)基于@Transactional注解。 @Transactional使用起来方便,但也需要注意引起@Transactional失效的场景,本文总结了七种情况,下面进行逐一分析: 一、数据库本身不支持 MySql 的 MyISAM 引擎不支持回滚,如果需要自动回滚事务,需要将MySql的引擎设置成I

weixin_43939924的博客 668

【OpenClaw从入门到精通】命令行操作指南——TUI模式高效管理OpenClaw(2026避坑版)

本文将详细介绍OpenClaw的命令行操作,重点介绍TUI(Text User Interface)模式的高效使用方法,以及在实际使用中需要避免的常见陷阱。通过本文的学习,你将掌握OpenClaw命令行的精髓,提升工作效率。

一键难忘的博客 1693

【后量子密码】SPHINCS+ 算法(二):签名与验签(附源码分析)

本节介绍SPHINCS+ 算法的密钥生成、签名与验签。nnn:安全参数;www:Winternitz参数,WOTS+签名分解的基底;hhh:超树的高度;ddd:超树中子树的层数;kkk:FORS 树的数量;ttt:FORS 树的叶子节点数。

听雨草堂 4680

Methods annotated with ‘@Async‘ must be overridable

Methods annotated with '@Async' must be overridable

weixin_44021888的博客 2170

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

美团二面:Spring的@Transactional如何实现的?

今天去看牙医,他问我年级轻轻牙齿怎么磨损这么严重?我说,没有点赞的这些年,我都是咬着牙过来的。@Transactional注解简介@Transactional是spring中声明式事务管理...

码猿技术专栏 217

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

@Transactional 注解的失效场景

作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。。下面码哥就来介绍常见的失效的场景。

smart_an的专栏 980

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

美团二面:Spring的@Transactional如何实现的

target = getTarget();if (target != null) {targetClass = target.getClass();}//followList chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;// Check whether we only have one InvokerInterceptor: that is,// no

2401_84048398的博客 949

@Transactional失效问题

一般来说,正向思维是想办法让updateUser()事务起效,但方法1却采用了逆向思维:让selectUser()的事务起效,从而把updateUser()放在一个更大的事务中,最终控制事务。由于test不存在事务,于是selectUser()也就没有事务,而this.updateUser()本身事务失效,所以最终整个调用事务失效。导致事务失效的根本原因在于,非事务方法中调用updateUser()本质上就是this.updateUser(),而this并不是代理对象,而是普通对象(后面再解释)。

smart_an的专栏 1691

为什么大厂一般不推荐使用@Transactional?

大厂禁用@Transactional声明式事务的原因在于其在高并发复杂业务中的隐蔽风险。核心问题包括事务范围过大、失效场景隐蔽、传播行为误用、锁竞争及分布式场景不友好等,容易导致数据不一致、性能瓶颈和排查困难。主流替代方案包括编程式事务TransactionTemplate、手动控制连接、最终一致性补偿机制等,强调事务边界的显式可见和可控性。大厂规范要求事务必须最小化、显式化,避免包含非DB操作,以降低资损风险。声明式事务虽便捷,但在高要求场景下风险收益比不佳。

likuoelie的博客 470

总结6种@Transactional注解的失效场景

Transactional 可以作用在接口类类方法。作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效@Autowired。

smart_an的专栏 1116
上一篇: 字节遭遇离职潮
下一篇: 从20s优化到500ms,我用了这三招
公众号-芋道源码
博客等级 码龄10年 1634粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值