百度一面:谈谈 @Transactional 的原理和坑

news2024/11/13 21:49:45

百度一面:谈谈 @Transactional 的原理和坑

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

项目准备

  • 下面是 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("事务生效");
        }
    }
    

事务不生效的几种 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() 前后不会进行任何增强操作,也就是类内部调用,不会通过代理方式访问。

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=女]
    

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();
    }
    
  • 由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。

源码解读

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

@Transactional 执行机制

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

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

  • 在这里插入图片描述

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

  • 在这里插入图片描述

  • 进入 invoke() 方法后:

  • 在这里插入图片描述

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

  • 在这里插入图片描述

private 导致事务不生效原因

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

  • 在这里插入图片描述

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

  • 在这里插入图片描述

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

  • 在这里插入图片描述

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

    • PUBLIC: 1
    • PRIVATE: 2
    • PROTECTED: 4
  • 在这里插入图片描述

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

异常不匹配原因

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

  • 在这里插入图片描述

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

  • 在这里插入图片描述

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

  • 在这里插入图片描述

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

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

  • 在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1080276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

js的BoM事件(二)

js的BoM事件&#xff08;二&#xff09;&#xff0c;上一篇的补充 一.alert,confirm二.prompt三.open四.close 一.alert,confirm 实例&#xff1a; alert(hi); confirm(message);二.prompt <body><button ></button><script>var btndocument.querySe…

dbeaver 插入别名设置禁用

1&#xff0c;前提 最近换了一个数据库连接工具&#xff0c;初次使用&#xff0c;非常别扭。 2&#xff0c;问题 首先遇到的第一个问题是 每次输入from table时&#xff0c;后面就会自动添加一个表别名 tt&#xff0c;然后语句就变成这样 from table tt &#xff0c;所以每次…

全面深入了解自动化测试

一、自动化测试 在软件测试中&#xff0c;自动化测试指的是使用独立于待测软件的其他软件来自动执行测试、比较实际结果与预期并生成测试报告这一过程。在测试流程已经确定后&#xff0c;测试自动化可以自动执行的一些重复但必要测试工作。也可以完成手动测试几乎不可能完成的…

飞凌嵌入式受邀参加「NXP创新技术论坛」

2023年10月10日&#xff0c;「NXP创新技术论坛」在深圳湾万丽酒店举行&#xff0c;飞凌嵌入式作为NXP金牌合作伙伴受邀参加此次论坛&#xff0c;与众多智能工业行业的伙伴深入交流市场趋势与行业洞察&#xff0c;共同促进未来市场的发展。 本次论坛&#xff0c;飞凌嵌入式展示了…

GaussDB向量数据库为盘古大模型再添助力

在今年7月7日的华为开发者大会2023(Cloud)期间,华为云盘古大模型3.0正式发布。目前盘古大模型已在政务、金融、制造、医药研发、气象等诸多行业发挥巨大价值。此次华为云发布的GaussDB向量数据库,具备一站式部署、全栈自主创新优势,不仅如此,它的ANN算法在行业排名第一,…

龙讯旷腾:如何建立基于第一性原理的正向研发模式,原子级计算伴随的时间和空间尺度增长将带来的变革

2023年10月8-11日&#xff0c;由中国材料研究学会主办的第四届中国新材料产业发展大会在浙江温州隆重举行。来自全国各地的6000余名新材料专家、企业家、投资家、当地高等院校和企事业单位的代表以及51位两院院士出席了本次大会。龙讯旷腾总经理吕海峰特邀做“先进材料数字化研…

点击、拖曳,15分钟搞定BI零售数据分析

早几年做数据分析还很依赖IT&#xff0c;过程复杂、耗时长、灵活性差&#xff0c;但这几年随着BI智能数据分析技术的成长&#xff0c;零售数据分析发生了翻天覆地的变化&#xff0c;其中最直观的一点就是&#xff1a;点击、拖曳&#xff0c;15分钟内就能搞定BI零售数据分析。 …

uCOSIII实时操作系统 五 任务API(任务创建和删除)

任务创建和删除 引入&#xff1a;一个任务的三要素是任务主体函数&#xff0c;任务栈&#xff0c;任务控制块&#xff0c;那么怎样吧这个三要素联系在一起呐&#xff1f; 任务创建&#xff1a; 在UCOSIII中我们通过函数OSTaskCreate()来创建任务。 作用&#xff1a;任务控制块…

有root权限的共享服务器,返现福利

以下是目前各类型服务器配置与价格目录&#xff1a; 可咨询文末微信号领取返现福利&#xff0c;注册链接&#xff1a; 西柚云超算https://www.xiyoucloud.net/aff/YADJJHWA 微信号&#xff1a;生信小博士

需求解析思路

需求&#xff1a;如果一个学生N天没学习了 根据question_user_submit_record&#xff08;N配置&#xff09;&#xff0c;公众号推送通知到java8c.com学习 由于写代码需要严谨快速&#xff1a; 当前写代码方式&#xff1a;先写controller&#xff0c;再写sql语句&#xff0c;…

Postman历史版本下载

1. 下载对应版本的postman 历史版本下载 请把下面链接的"版本号"替换为指定的版本号&#xff0c;例如&#xff1a;8.8.0 Windows64位 ​https://dl.pstmn.io/download/version/版本号/win64​ Windows32位 https://dl.pstmn.io/download/version/版本号…

SpringBoot+Dubbo+Nacos 开发Demo

1、是什么 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架&#xff0c;使得应用可通过高性能的RPC实现服务的输出和输入功能&#xff0c;可以和Spring框架无缝集成。Dubbo是一款高性能、轻量级的开源Java RPC框架&#xff0c;它提供了三大核心能力&#xff1a;面向接口的…

MinIO的安装与使用

文章目录 1.MINIO是什么&#xff1f;2.MINIO安装3.启动脚本4.打开MINIO页面5.MC命令6.MINIO备份脚本 1.MINIO是什么&#xff1f; MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。 MinIO与…

智能导诊系统、智能在线问诊系统源码

一、需求背景 目前各大城市的著名医院吸引越来越多的患者&#xff0c;咨询台的服务需求也随之增加。医院虽然设置了少数咨询台&#xff0c;但由于患者人数众多&#xff0c;咨询台往往无法满足患者咨询要求&#xff0c;护士工作量巨大&#xff0c;医院巨大的规模也让咨询台数量…

佳音通讯400电话中心:在线自选,惠及企业

在当今竞争激烈的商业环境中&#xff0c;企业需要提供卓越的客户服务来脱颖而出。而一个高效的400电话中心则成为了越来越多企业的选择。佳音通讯400电话中心官方网站是企业选择400电话服务的首选平台&#xff0c;提供了在线自选功能&#xff0c;让企业能够根据自身需求灵活选择…

实现一个自己的脚手架教程

前言 脚手架并不实现&#xff0c;难的是最佳实践的整理和沉淀。本文不会涉及到最佳实践方面的内容&#xff0c;只是教会你如何实现一个最基础的脚手架&#xff0c;以此作为展示最佳实践的载体。 如何搭建一个脚手架的工程 如何开发和调试一个脚手架 脚手架中如何接收和处理命…

C++教程(2)

C 环境设置 本地环境设置 如果您想要设置 C 语言环境&#xff0c;您需要确保电脑上有以下两款可用的软件&#xff0c;文本编辑器和 C 编译器。 文本编辑器 这将用于输入您的程序。文本编辑器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。 文…

零代码编程:用ChatGPT批量调整文件名称中的词汇顺序

文件夹里面很多文件&#xff0c;需要批量挑战标题中的一些词组顺序&#xff1a;“Peppa Pig - Kylie Kangaroo (14 episode _ 4 season) [HD].mp4”这个文件名改成“14 episode _ 4 season _ Peppa Pig - Kylie Kangaroo.mp4”&#xff0c;可以在ChatGPT中输入提示词&#xff1…

加盐加密详解

一、什么是加盐加密 加盐加密就是后端在存储一个密码的时候&#xff0c;为了提高安全性&#xff0c;随机生成一个盐值&#xff08;随机值&#xff09;&#xff0c;将盐值和密码进行有规则的结合&#xff0c;然后将结合过后的数据使用加密算法&#xff08;一般是md5加密&#x…

数据中心供配电及能效管理系统的设计要点

摘要&#xff1a;现代的数据中心中都包括大量的计算机&#xff0c;对于这种场所的电力供应&#xff0c;都要求供电系统需要在所有的时间都有效&#xff0c;这就不同于一般建筑的供配电系统&#xff0c;它是一个交叉的系统&#xff0c;涉及到市电供电、防雷接地、防静电、UPS不间…