Spring之@Transactional源码解析

news2025/1/15 13:15:26

前言

我们在日常开发的时候经常会用到组合注解,比如:@EnableTransactionManagement + @Transactional、@EnableAsync + @Async、@EnableAspectJAutoProxy + @Aspect。今天我们就来抽丝剥茧,揭开@Transactional注解的神秘面纱

@EnableTransactionManagement注解的作用

当我们看到类似@Enablexxx这样的注解,一般源码中都会存在@Import注解。@Import注解在Spring的解析阶段有着十分重要的地位,是Spring的一个重要的扩展点。其注入的class一般继承ImportSelectorImportBeanDefinitionRegistrar接口,作用分别如下

  • 继承ImportSelector接口:selectImports方法返回的类名数组会被解析成bean
  • 继承ImportBeanDefinitionRegistrar接口:会在解析阶段执行registerBeanDefinitions方法

感兴趣的小伙伴可以阅读下方链接对应博文,该博文主要讲解了Spring对@ComponentScan、@Import、@PropertySource、@Bean等注解的解析流程,可以更好的帮助我们理解本篇文章Spring之ConfigurationClassPostProcessor解析流程icon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/134761961?spm=1001.2014.3001.5501

1.@EnableTransactionManagement注解源码

 

通过上面的源码,我们简单理论分析

@EnableTransactionManagement注解会注入一个类型为TransactionManagementConfigurationSelector的class,该class的父类实现selectImports方法,父类方法又会调用子类的同名方法。根据上文中阐述的@Import注解的作用,此时Spring容器中多了两个BeanDefinition:一个beanClass为AutoProxyRegistrar,另一个beanClass为ProxyTransactionManagementConfiguration

结论1:@EnableTransactionManagement注解会import一个类型为TransactionManagementConfigurationSelector的class,该class实现ImportSelector接口,其接口方法返回[AutoProxyRegistrar,ProxyTransactionManagementConfiguration]类名数组,即Spring容器在后期会存在beanClass为AutoProxyRegistrar和ProxyTransactionManagementConfiguration的两个bean

2.AutoProxyRegistrar源码

AutoProxyRegistrar实现ImportBeanDefinitionRegistrar接口,所以会在Spring的解析阶段执行registerBeanDefinitions方法,我们重点关注截图框住的方法,它会注入一个beanClass为InfrastructureAdvisorAutoProxyCreator的BeanDefinition。InfrastructureAdvisorAutoProxyCreator是BeanPostProcessor(后文简称bpp)的子类,它会在普通bean的生命周期对bean进行一些干预,比如当前bpp就会在Spring执行bpp的postProcessAfterInitialization方法的时候会对bean进行动态代理(这个我们后文分析)

结论2:AutoProxyRegistrar会注入一个类型为InfrastructureAdvisorAutoProxyCreator的bean

3.ProxyTransactionManagementConfiguration源码

这个类比较简单,就是一个配置类,利用@Configuration + @Bean的组合,创建了几个bean

结论3:ProxyTransactionManagementConfiguration会注入beanClass为BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource、TransactionInterceptor的三个bean

小结

@EnableTransactionManagement注解会import一个实现ImportSelector接口的类,import的类会注入两个bean(beanClass分别为AutoProxyRegistrar和ProxyTransactionManagementConfiguration),其中AutoProxyRegistrar会进一步解析,然后注入一个类型为InfrastructureAdvisorAutoProxyCreator的bpp。ProxyTransactionManagementConfiguration是一个配置类,会注入几个bean,协助@EnableTransactionManagement注解完成相关功能

2.什么是BeanPostProcessor

BeanPostProcessor从本质上说,它也是一个bean,不过它优先实例化,然后作用于普通bean。比如我们耳熟能详的属性注入、动态代理,都是BeanPostProcessor在不同阶段对普通bean进行的处理。

详情阅读下方链接博文
Spring之BeanPostProcessoricon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/134753005?spm=1001.2014.3001.5502

3.InfrastructureAdvisorAutoProxyCreator的作用

InfrastructureAdvisorAutoProxyCreator继承BeanPostProcessor,在spring执行到postProcessAfterInitialization的时候会查找可以作用于当前bean的Advisors,如果存在符合条件的Advisors,则进行动态代理

具体查找过程可以查看下方链接博文。简单来说就是查找普通bean所属的class和方法上有没有@Transactional注解,如果满足条件则进行AOP动态代理。Spring之AOP源码解析(下)icon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/136182213?spm=1001.2014.3001.5502

根据我们设置的参数,有可能进行JDK动态代理也有可能进行cglib动态代理,如果是JDK动态代理我们关注JdkDynamicAopProxy这个类,如果是cglib动态代理我们关注DynamicAdvisedInterceptor这个类。不管是什么动态代理都会有一个field(advised),这个参数存储了可以作用于当前bean的Advisors,每个Advisor都有一个advice对象(MethodInterceptor的父接口),Spring会将这些advice串成一个拦截器链,链式调用各个拦截器的invoke方法,我们画图演示流程

PS:有兴趣的小伙伴可以把我写的几篇关于AOP的文章都阅读一下,可以更好的帮助我们理解这篇博文。

 4.TransactionInterceptor源码

主要关注其invoke方法,invoke方法主要调用了invokeWithinTransaction方法

主体流程

    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                             final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {

        // 上文分析的ProxyTransactionManagementConfiguration注入的三个bean之一(AnnotationTransactionAttributeSource)
        TransactionAttributeSource tas = getTransactionAttributeSource();
        // 1.从类上查找@Transactional注解
        // 2.从方法上查找@Transactional注解
        // 3.将步骤1或2查找到的@Transactional注解进行解析,构建成TransactionAttribute对象(RuleBasedTransactionAttribute)
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);

        // 1.如果txAttr为null或者beanFactory为null,返回注入的TransactionManager
        // 2.如果txAttr.getQualifier不为null(即Transactional注解的value属性值),则从beanFactory获取配置的bean返回
        // 3.如果注入了transactionManagerBeanName则从beanFactory获取bean返回
        // 4.如果没注入TransactionManager,就从beanFactory获取class为PlatformTransactionManager的bean返回
        final TransactionManager tm = determineTransactionManager(txAttr);

        // 省略webFlux相关代码

        PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 开启事务(如果需要)
            TransactionAspectSupport.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                // 执行下一个拦截器的invoke方法或者目标方法
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable ex) {
                // 处理异常
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            } finally {
                // 解除线程和事务的绑定关系
                cleanupTransactionInfo(txInfo);
            }

            if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
                // Set rollback-only in case of Vavr failure matching our rollback rules...
                TransactionStatus status = txInfo.getTransactionStatus();
                if (status != null && txAttr != null) {
                    retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                }
            }
            // 提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }

    }
  1. 基础数据准备
    1. 获取TransactionAttributeSource
    2. 获取TransactionAttribute
    3. 推断TransactionManager
  2. 开启事务
  3. 执行拦截器方法或者目标方法
  4. 处理异常(如果存在)
  5. 提交事务

事务同步管理器TransactionSynchronizationManager

这个类在@Transactional源码中起着重要作用,它不仅管理每个线程的资源和事务同步,也协助完成与mybatis的集成

开启事务

TransactionAspectSupport#createTransactionIfNecessary

AbstractPlatformTransactionManager#getTransaction

AbstractPlatformTransactionManager#startTransaction

DataSourceTransactionManager#doBegin

    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject) transaction;
        Connection con = null;

        try {
            // 如果事务还没有获取Connection或者Connection还没标记为与事务同步
            if (!txObject.hasConnectionHolder() ||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                // 获取Connection
                Connection newCon = obtainDataSource().getConnection();
                if (logger.isDebugEnabled()) {
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                // 将Connection标记为new Connection
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

            // 将Connection没标记为与事务同步
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();

            // 设置事务的隔离级别
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            txObject.setReadOnly(definition.isReadOnly());

            // 将Connection的自动提交关闭
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }

            prepareTransactionalConnection(con, definition);
            // 将事务标记为已激活
            txObject.getConnectionHolder().setTransactionActive(true);

            // 设置过期时间(如果手动设置了)
            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }

            // 如果是新连接,事务同步管理器同步资源
            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
            }
        }

        catch (Throwable ex) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, obtainDataSource());
                txObject.setConnectionHolder(null, false);
            }
            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
        }
    }

PS : Spring源码中存在很多模板模式,很多方法都是交给子类去实现,比如上文中的doGetTransaction,isExistingTransaction,doBegin等方法,如果我们不确定具体走的是那个子类,可以多多去debug看看。这里TransactionManager主要是子类DataSourceTransactionManager

注意:我们在mysql中开启事务,可以使用BEGIN、START TRANSACTION等,但是在源码中,我们并没有发现这样的sql语句。其实事务可以隐式开启,在上述doBegin方法的源码中,存在con.setAutoCommit(false)这样的方法,其实这就等价于执行sql语句set autocommit = OFF(隐式开启事务)

事务隔离级别

  • REQUIRED
  • SUPPORTS
  • MANDATORY
  • REQUIRES_NEW
  • NOT_SUPPORTED
  • NEVER
  • NESTED

通过源码整理事务处理流程

异常处理

TransactionAspectSupport#completeTransactionAfterThrowing

我们在上文中指出该TransactionAttribute类型为RuleBasedTransactionAttribut,然后如果我们指定@Transactional注解的rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,后期会被解析成RollbackRuleAttribute对象,相关源码明细可以查看AbstractFallbackTransactionAttributeSource(AnnotationTransactionAttributeSource的父类)的getTransactionAttribute方法

case1:未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

当我们未指定上述四个属性,会调用super.rollbackOn的方法

当我们未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性的时候,事务只有在遇到RuntimeException异常或者Error的时候才会回滚

case2:指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

当我们指定了四个属性中的一个或者多个,就会被解析成RollbackRuleAttribute(NoRollbackRuleAttribute),最后通过getDepth方法获取winner,如果winner是RollbackRuleAttribute旧回滚,否则就提交事务

getDepth方法主要判断抛出的异常与指定的异常之间的关系

  • 如果抛出异常和指定异常一致,则depth为0
  • 如果抛出异常是指定异常子类,则depth加1(递归判断)
  • 如果抛出异常为Throwable,则depth为-1

depth值越小(大于0),优先级越高

触发器

不管最后事务是提交还是回滚,都会执行相应的触发器方法,我们可以利用这一特性,做一些扩展

与mybatis的整合

mybatis相关接口会被JDK动态代理,代理对象的类型是MapperProxy。感兴趣小伙伴可以阅读我之前的博文 《@MapperScan源码解析》

MapperProxy#invoke

PlainMethodInvoker#invokeMapperMethod#executeSqlSessionTemplate#selectOne
SqlSessionInterceptor#invoke(sqlSessionProxy是一个代理对象,所以会进入相应拦截器方法)SqlSessionUtils#getSqlSession 

我们看到了我们熟悉的TransactionSynchronizationManager,当mybatis执行sql的时候会从事务同步管理器里面获取resource,保证了同个事务里面的增删改查都是使用的同一个SqlSession

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

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

相关文章

nodejs web服务器 之初始化路由

每当一个请求到达服务器之后&#xff0c;需要先经过路由的匹配&#xff0c;只有匹配成功之后&#xff0c;才会调用对应的处理函数。在匹配时&#xff0c;会按照顺序进行匹配&#xff0c;请求类型和请求的URL同时匹配成功&#xff0c;返回对应的数据。 我们可以创建一个js文件&a…

零售EDI:劳氏 Lowe‘s EDI项目案例

通过 EDI&#xff0c;企业与Lowes之间可以直接交换各种商业文档&#xff0c;如订单、发票、收据等&#xff0c;从而实现信息的实时交换&#xff0c;提高了供应链的效率和准确性。在现代供应链管理中&#xff0c;EDI 已经成为了不可或缺的重要工具。 作为一家拥有多条业务线的企…

【数据结构】矩阵的压缩存储

矩阵的压缩存储 5.1 普通矩阵的存储 用二维数组存储 分为行优先和列优先&#xff1a; 行优先&#xff1a;优先存放一行的数据。 列优先&#xff1a;优先存放一列的数据。 注意下标是从0还是1开始的&#xff01; 5.2 对称矩阵的存储 对称矩阵定义 若n阶方阵中任意一个元素 a i …

【Python】新手入门(8):什么是迭代?迭代的作用是什么?

【Python】新手入门&#xff08;8&#xff09;&#xff1a;什么是迭代&#xff1f;迭代有什么应用&#xff1f; &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】…

C++读取NC数据的结果与真实数值不一致的解决方法

本文介绍基于C 语言的netCDF库读取.nc格式的栅格文件时&#xff0c;代码读取到的数据与栅格文件的实际数据不一致的解决方法。 最近&#xff0c;由于需要读取ERA5气象数据&#xff0c;因此使用C 语言中的netCDF库读取.nc格式文件。其中&#xff0c;偶然发现在Visual Studio的代…

卷积神经网络(CNN)算法详解

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 引言 卷积神经网络&#xff08;Convolutional Neural Networks, CNN&#xff09;是一类包含卷积计算且具有深度结构的前馈神经网络&#xff08;…

Android Termux系统安装openssh实现公网使用SFTP远程访问

文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 SFTP&#xff08;SSH File Transfer Protocol&#xff09;是一种基于SSH&#xff08;Secure Shell&#xff09;安全协议的文件传输协议。与FTP协议相比&#xff0c;SFTP使用了…

外包干了5天,技术退步明显。。。。。

在湖南的一个安静角落&#xff0c;我&#xff0c;一个普通的大专生&#xff0c;开始了我的软件测试之旅。四年的外包生涯&#xff0c;让我在舒适区里逐渐失去了锐气&#xff0c;技术停滞不前&#xff0c;仿佛被时间遗忘。然而&#xff0c;生活的转机总是在不经意间降临。 与女…

【系统学习】2-Java进阶知识总结-3-集合-1-补充【泛型、树、数据结构】

文章目录 泛型什么是泛型&#xff1f;常见的泛型标识符泛型类泛型方法泛型接口通配符 树树的基本概念什么是二叉树&#xff1f;二叉树--普通二叉树二叉树--二叉查找树定义规则优缺点 二叉树--平衡二叉树定义规则旋转机制 二叉树--红黑树定义规则红黑规则 常见数据结构总体特点结…

node的安装与介绍

安装 下载地址 node官网首页就会有两个安装选择&#xff0c;会根据当前电脑的系统自动显示对应的安装包&#xff0c;一个长期维护版&#xff08;LTS&#xff09;,一个是尝鲜版&#xff0c;记住选择LTS版本 安装指定版本下载截图 安装过程截图&#xff08;非常简单&#xff…

STM32CubeMX学习笔记14 ---SPI总线

1. 简介 1.1 SPI总线介绍 SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在…

掌握Linux之巅:RHCE认证快速攻略

在数字化时代&#xff0c;Linux系统已经成为企业级应用的重要支柱。RHCE&#xff08;Red Hat Certified Engineer&#xff09;认证&#xff0c;作为Linux领域的权威认证&#xff0c;不仅代表了专业技术的认可&#xff0c;更是职业发展的有力武器。本文将为你揭秘如何快速掌握Li…

@Autoweird和@Resourse的区别 java定义Bean的方式

Autoweird private Apple apple; Autoweird首先是根据类型来找 就是这个Apple 如果找到多个 会在根据名称就是这个apple来找&#xff0c;如果再找不到&#xff0c;就报错 Resourse相反 举例说明&#xff1a; 我们使用Autoweird ZhouyuService zhouyuService Resourse特别之…

C语言题目练习

目录 前言 1、转置矩阵 1.1 题目 描述 输入描述&#xff1a; 输出描述&#xff1a; 1.2解题 分析&#xff1a; 程序&#xff1a; 2、KiKi判断上三角矩阵 2.1 题目 描述 输入描述&#xff1a; 输出描述&#xff1a; 2.2 解题 分析&#xff1a; 程序&#xff1a; 3、…

什么是工业交换机?

如今&#xff0c;工业交换机在能源、环保、交通、智慧城市监控等各个行业都发挥着至关重要的作用&#xff0c;其需求也日益增长。本文将全面介绍工业交换机&#xff0c;帮助你进一步加深了解。 什么是工业交换机&#xff1f; 工业交换机&#xff0c;又称工业以太网交换机&…

基于微信小程序的电影院订票选座系统的设计与实现(程序+数据库+)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…

轻薄蓝牙工牌室内人员定位应用

在现代化企业管理的背景下&#xff0c;轻薄蓝牙工牌人员定位应用逐渐崭露头角&#xff0c;成为提升企业效率和安全性的重要工具。本文将从轻薄蓝牙工牌的定义、特点、应用场景以及未来发展趋势等方面&#xff0c;对其进行全面深入的探讨。 一、轻薄蓝牙工牌的定义与特点 轻薄…

Vue:纯前端实现文件拖拽上传

先看一下拖拽相关的事件&#xff1a;dragover、dragenter drop和dragleave 。 dragover事件&#xff1a;当被拖动的元素在一个可放置目标上方时&#xff0c;该事件会被触发。 通常&#xff0c;我们会使用event.preventDefault()方法来取消浏览器默认的拖放行为&#xff0c;以便…

赛事通知丨2024年(第12届)“泰迪杯”挑战赛即将开始

2024年&#xff08;第12届&#xff09;“泰迪杯”数据挖掘挑战赛将于3月8日开放报名。“泰迪杯”数据挖掘挑战赛始创于2013年&#xff0c;迄今已经连续举办了12年。累计参赛高校千余所&#xff0c;累计参赛人数逾10万人&#xff0c;全国各省份均有参加。大赛的开展始终秉持推动…

局域网管理工具

每个组织的业务运营方法都是独一无二的&#xff0c;其网络基础设施也是如此&#xff0c;由于随着超融合基础设施等新计算技术的发展&#xff0c;局域网变得越来越复杂&#xff0c;因此局域网管理也应该如此&#xff0c;组织需要量身定制的局域网管理解决方案&#xff0c;这些解…