61张图,图解Spring事务,拆解底层源码

news2024/11/16 23:33:04

下面我会简单介绍一下 Spring 事务的基础知识,以及使用方法,然后直接对源码进行拆解。

不 BB,上文章目录。

​1. 项目准备

需要搭建环境的同学,代码详见:https://github.com/lml200701158/program_demo/tree/main/spring-transaction

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

uidunameusex1张三女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 Louzai {
    @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("事务生效");
    }
}

执行入口:

public class SpringMyBatisTest {
    public static void main(String[] args) throws Exception {
        String xmlPath = "applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        Louzai uc = (Louzai) applicationContext.getBean("louzai");
        uc.testSuccess();
    }
}

输出:

16:44:38.267 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
16:44:38.363 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'txManager'
16:44:40.966 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.mybatis.controller.Louzai.testSuccess]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception
16:44:40.968 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/java_study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai]
16:44:41.228 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@5b5caf08] for JDBC transaction
16:44:41.231 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5b5caf08] to manual commit
原记录:MyUser(uid=1, uname=张三, usex=女)
16:42:59.345 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
16:42:59.346 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@70807224]
16:42:59.354 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@70807224] after transaction
Exception in thread "main" java.lang.Exception: 事务生效
 at com.mybatis.controller.Louzai.testSuccess(Louzai.java:34)
  // 异常日志省略...

2. Spring 事务工作流程

为了方便大家能更好看懂后面的源码,我先整体介绍一下源码的执行流程,让大家有一个整体的认识,否则容易被绕进去。

整个 Spring 事务源码,其实分为 2 块,我们会结合上面的示例,给大家进行讲解。

​第一块是后置处理,我们在创建 Louzai Bean 的后置处理器中,里面会做两件事情:

获取 Louzai 的切面方法:首先会拿到所有的切面信息,和 Louzai 的所有方法进行匹配,然后找到 Louzai 所有需要进行事务处理的方法,匹配成功的方法,还需要将事务属性保存到缓存 attributeCache 中。

创建 AOP 代理对象:结合 Louzai 需要进行 AOP 的方法,选择 Cglib 或 JDK,创建 AOP 代理对象。

​第二块是事务执行,整个逻辑比较复杂,我只选取 4 块最核心的逻辑,分别为从缓存拿到事务属性、创建并开启事务、执行业务逻辑、提交或者回滚事务。

3. 源码解读

注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不一样!!!

上面的知识都不难,下面才是我们的重头戏,让你跟着楼仔,走一遍代码流程。

3.1 代码入口

这里需要多跑几次,把前面的 beanName 跳过去,只看 louzai。

​进入 doGetBean(),进入创建 Bean 的逻辑。

​进入 createBean(),调用 doCreateBean()。

进入 doCreateBean(),调用 initializeBean()。

​如果看过我前面几期系列源码的同学,对这个入口应该会非常熟悉,其实就是用来创建代理对象。

3.2 创建代理对象

​这里是重点!敲黑板!!!

  1. 先获取 louzai 类的所有切面列表;

  2. 创建一个 AOP 的代理对象。

​3.2.1 获取切面列表

​这里有 2 个重要的方法,先执行 findCandidateAdvisors(),待会我们还会再返回 findEligibleAdvisors()。

​依次返回,重新来到 findEligibleAdvisors()。

​进入 canApply(),开始匹配 louzai 的切面。

这里是重点!敲黑板!!!

这里只会匹配到 Louzai.testSuccess() 方法,我们直接进入匹配逻辑。

如果匹配成功,还会把事务的属性配置信息放入 attributeCache 缓存。

​我们依次返回到 getTransactionAttribute(),再看看放入缓存中的数据。

​再回到该小节开头,我们拿到 louzai 的切面信息,去创建 AOP 代理对象。

3.2.2 创建 AOP 代理对象

创建 AOP 代理对象的逻辑,在上一篇文章(Spring AOP)讲解过,我是通过 Cglib 创建,感兴趣的同学可以关注公众号「楼仔」,翻一下楼仔的历史文章。

3.3 事务执行

回到业务逻辑,通过 louzai 的 AOP 代理对象,开始执行主方法。

​因为代理对象是 Cglib 方式创建,所以通过 Cglib 来执行。

这里是重点!敲黑板!!!

下面的代码是事务执行的核心逻辑 invokeWithinTransaction()。

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

        //获取我们的事务属源对象
        TransactionAttributeSource tas = getTransactionAttributeSource();
        //通过事务属性源对象获取到我们的事务属性信息
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        //获取我们配置的事务管理器对象
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        //从tx属性对象中获取出标注了@Transactionl的方法描述符
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        //处理声明式事务
        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            //有没有必要创建事务
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                //调用钩子函数进行回调目标方法
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                //抛出异常进行回滚处理
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                //清空我们的线程变量中transactionInfo的值
                cleanupTransactionInfo(txInfo);
            }
            //提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
        //编程式事务
        else {
          // 这里不是我们的重点,省略...
        }
    }

3.3.1 获取事务属性

在 invokeWithinTransaction() 中,我们找到获取事务属性的入口。

​从 attributeCache 获取事务的缓存数据,缓存数据是在 “2.2.1 获取切面列表” 中保存的。

3.3.2 创建事务

​通过 doGetTransaction() 获取事务。

protected Object doGetTransaction() {
        //创建一个数据源事务对象
        DataSourceTransactionObject txObject = new DataSourceTransactionObject();
        //是否允许当前事务设置保持点
        txObject.setSavepointAllowed(isNestedTransactionAllowed());
        /**
         * TransactionSynchronizationManager 事务同步管理器对象(该类中都是局部线程变量)
         * 用来保存当前事务的信息,我们第一次从这里去线程变量中获取 事务连接持有器对象 通过数据源为key去获取
         * 由于第一次进来开始事务 我们的事务同步管理器中没有被存放.所以此时获取出来的conHolder为null
         */
        ConnectionHolder conHolder =
                (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
        txObject.setConnectionHolder(conHolder, false);
        //返回事务对象
        return txObject;
    }

通过 startTransaction() 开启事务。

下面是开启事务的详细逻辑,了解一下即可。

protected void doBegin(Object transaction, TransactionDefinition definition) {
        //强制转化事务对象
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = null;

        try {
            //判断事务对象没有数据库连接持有器
            if (!txObject.hasConnectionHolder() ||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                //通过数据源获取一个数据库连接对象
                Connection newCon = obtainDataSource().getConnection();
                if (logger.isDebugEnabled()) {
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                //把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

            //标记当前的连接是一个同步事务
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();

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

            //关闭自动提交
            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);
            }

            // 绑定我们的数据源和连接到我们的同步管理器上   把数据源作为key,数据库连接作为value 设置到线程变量中
            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);
        }
    }

CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); } }

最后返回到 invokeWithinTransaction(),得到 txInfo 对象。

3.3.3 执行逻辑

还是在 invokeWithinTransaction() 中,开始执行业务逻辑。

​进入到真正的业务逻辑。

​执行完毕后抛出异常,依次返回,走后续的回滚事务逻辑。

3.3.4 回滚事务

还是在 invokeWithinTransaction() 中,进入回滚事务的逻辑。

​执行回滚逻辑很简单,我们只看如何判断是否回滚。

如果抛出的异常类型,和事务定义的异常类型匹配,证明该异常需要捕获。

之所以用递归,不仅需要判断抛出异常的本身,还需要判断它继承的父类异常,满足任意一个即可捕获。

​到这里,所有的流程结束。

4. 结语

我们再小节一下,文章先介绍了事务的使用示例,以及事务的执行流程。

之后再剖析了事务的源码,分为 2 块:

  • 先匹配出 louzai 对象所有关于事务的切面列表,并将匹配成功的事务属性保存到缓存;

  • 从缓存取出事务属性,然后创建、启动事务,执行业务逻辑,最后提交或者回滚事务。

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

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

相关文章

09-18-k8s-二进制方式搭建

09-k8s-二进制方式搭建&#xff1a; 1、创建多台虚拟机&#xff0c;安装Linux操作系统 &#xff08;1&#xff09;一台或多台机器&#xff0c;操作系统 CentOS7.x-86_x64 &#xff08;2&#xff09;硬件配置&#xff1a;2GB 或更多 RAM&#xff0c;2 个 CPU 或更多 CPU&…

大数据_数据中台_数据汇聚联通

目录 一、数据采集、汇聚的方法和工具 1、线上行为采集 2、线下行为采集 3、互联网数据采集 4、内部数据汇聚 二、数据交换产品 1、数据源管理 2、离线数据交换 3、实时数据交换 三、数据存储的选择 1、在线与离线 2、OLTP与OLAP 3、存储技术 构建企业级的数据中台…

Java swing(GUI) mysql实现的仓库进销存管理系统源码+运行教程

今天给大家介绍下由Java swing mysql实现的一款仓库库存管理系统&#xff0c;该系统实现了基本的仓库进退货管理、用户管理等操作&#xff0c;主要涉及的知识点有&#xff1a;Java swing awt界面编程、数据库的基本操作&#xff08;增删改查&#xff09;&#xff0c;多线程等&a…

第十二章:synchronized与锁升级

相关面试题锁优化背景Synchronized 锁性能变化jdk5 以前复习&#xff1a;为什么任意一个对象都能成为锁&#xff1f;jdk6 之后synchronized的种类以及锁升级流程锁升级流程无锁偏向锁是什么作用小总结偏向锁的持有偏向锁 JVM 参数说明多线程环境下总结轻量级锁轻量级锁的获取代…

Java成员方法的声明和调用

声明成员方法可以定义类的行为&#xff0c;行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的&#xff0c;属性只不过提供了相应的数据。 一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型&#xff0c;其结…

6-脱氧-β- L -半乳吡喃糖基鸟苷 5′-二磷酸,Guanosine 5‘-diphospho-fucose,GDP-BETA-L-FUCOSE

产品名称&#xff1a;6-脱氧-β- L -半乳吡喃糖基鸟苷 5′-二磷酸&#xff0c;GDP-L-岩藻糖&#xff0c;GDP-L-FUCOSE二钠盐 英文名称&#xff1a;Guanosine 5-diphospho-fucose&#xff0c;GDP-BETA-L-FUCOSE&#xff0c;GDP-L-Fuc.2Na CAS号:148296-47-3 英文同义词:Guanos…

【深度学习】常用算法生成对抗网络、自编码网络、多层感知机、反向传播等讲解(图文解释 超详细)

觉得有帮助请点赞关注收藏~~~ 一、生成对抗网络GAN Generative Adversarial Network 两个组件组成&#xff1a;一个生成器&#xff0c;用于生成虚拟数据&#xff0c;另一个是鉴别器&#xff0c;用于(GAN)生成式深度学习算法&#xff0c;可创建类似于训练数据的新数据实例。 G…

2022-kaggle-nlp赛事:Feedback Prize - English Language Learning

文章目录零、比赛介绍0.1 比赛目标0.2 数据集0.3 注意事项一、设置1.1 导入相关库1.2 设置超参数和随机种子1.3 启动wandb二、 数据预处理2.1 定义前处理函数&#xff0c;tokenizer文本2.2 定义Dataset&#xff0c;并将数据装入DataLoader三、辅助函数四、池化五、模型六、定义…

jmeter-事务控制器与并发控制器与if控制器项目实践

前言 在做性能压测的时候&#xff0c;除了做单接口这种基准压测&#xff0c;我们还需要多接口串联的混合场景&#xff0c;比如打开小程序展示的首页&#xff0c;购物下单时的结算页。如果这些接口都是串行的&#xff0c;那就非常简单了&#xff0c;仅仅只需要创建事务控制器&a…

【GD32F427开发板试用】+rtt-thread nano+finsh极简开发

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;理想三旬 引言 在工作闲暇之际&#xff0c;逛逛论坛&#xff0c;无意间看到GD的试用活动&#xff0c;一如既往的积极&#xff0c;在官方还没发…

Linux 驱动的内核适配 - 方法

原生与野生 Linux 的驱动代码大致可分为两种&#xff1a;一种是已经进入 mainline 的&#xff0c;当内核 API 变化时&#xff0c;会被同步地修改&#xff1b;还有一种是 out-of-tree 的&#xff0c;需要用一套驱动代码去适配不同版本的内核。由于内核 API 持续变动的特性&…

带你实现react源码的核心功能

React 的几种组件以及首次渲染实现React 更新机制的实现以及 React diff 算法 React 的代码还是非常复杂的&#xff0c;虽然这里是一个简化版本。但是还是需要有不错的面向对象思维的。React 的核心主要有一下几点。 虚拟 dom 对象&#xff08;Virtual DOM&#xff09;虚拟 d…

RabbitMQ_消息确认机制

消息确认机制分为消息发送确认机制与消息消费确认机制 消息发送确认机制 消息发送确认机制&#xff1a;消息由producer发送后&#xff0c;确认其是否到达broker&#xff0c;又是否被exchange转发至对应queue的机制 该机制分为两部分&#xff1a;producer---broker&#xff0c…

Android 性能优化之内存优化——重识内存

我们知道&#xff0c;手机的内存是有限的&#xff0c;如果应用内存占用过大&#xff0c;轻则引起卡顿&#xff0c;重则导致应用崩溃或被系统强制杀掉&#xff0c;更严重的情况下会影响应用的留存率。因此&#xff0c;内存优化是性能优化中非常重要的一部分。但是&#xff0c;很…

66-86-javajvm-堆

66-javajvm-堆&#xff1a; 堆的核心概述 堆与进程、线程 一个进程对应一个JVM实例一个JVM实例对应一个堆空间进程包含多个线程&#xff0c;所以线程之间共享同一个堆空间 对堆的认识 一个JVM实例只存在一个堆内存&#xff0c;堆也是Java内存管理的核心区域。Java堆区在JVM启动…

HashMap原理

在Java编程语言中&#xff0c;最基本的结构就是两种&#xff0c;一种是数组&#xff0c;一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造&#xff0c;HashMap也一样。当程序试图将多个 key-value 放入 HashMap 中时&#xff0c;以如下代码片段为例&#xff1a;…

P1182 数列分段 Section II——二分答案

数列分段 Section II 题目描述 对于给定的一个长度为N的正整数数列 A1∼NA_{1\sim N}A1∼N​&#xff0c;现要将其分成 MMM&#xff08;M≤NM\leq NM≤N&#xff09;段&#xff0c;并要求每段连续&#xff0c;且每段和的最大值最小。 关于最大值最小&#xff1a; 例如一数列…

NCTF web总结与复现

前言 打完NCTF休息了一下&#xff0c;总体感觉还行&#xff0c;学到了很多。 calc 这一题也卡了我很久&#xff0c;因为复现过DASCTF三月赛&#xff0c;一直在想着有没有可以替代反引号或绕过的方法&#xff0c;搞了好久都没出&#xff0c;在学长的提示下学到了一个方法&…

最新出炉的阿里巴巴面试题及答案汇总(513页)

前言 秋招已经结束了&#xff0c;不知道各位有没有拿到自己心仪的offer&#xff1f;最近有不少粉丝去阿里巴巴面试了&#xff0c;回来之后我整理成了一份手册java面试时常用到的面试题&#xff08;附答案&#xff09;那么今天分享给大家&#xff0c;祝愿大家都能找到满意的工作…

HTML期末作业课程设计期末大作业——我的美丽家乡湛江 海鲜之都HTML+CSS+JavaScript

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…