news 2026/5/12 13:24:02

挑战一篇文章带你图解Spring事务拆解底层源码!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
挑战一篇文章带你图解Spring事务拆解底层源码!

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

不 BB,上文章目录。

1.

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); } }

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

3.3.3 执行逻辑

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

进入到真正的业务逻辑。

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

3.3.4 回滚事务

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

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

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

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

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

4. 结语

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

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

  • 先匹配出 louzai 对象所有关于事务的切面列表,并将匹配成功的事务属性保存到缓存;
  • 从缓存取出事务属性,然后创建、启动事务,执行业务逻辑,最后提交或者回滚事务。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/12 8:42:49

专科生必看!千笔,最受欢迎的AI论文网站

你是否曾为论文选题发愁&#xff0c;绞尽脑汁却难以下笔&#xff1f;是否在深夜面对空白文档无从下手&#xff0c;反复修改仍不满意&#xff1f;论文写作的每一个环节都像一座难以逾越的高山&#xff0c;让无数自考学生倍感压力。别再独自挣扎&#xff0c;千笔AI&#xff0c;作…

作者头像 李华
网站建设 2026/5/12 8:42:50

Flutter-OH 核心概念:Package(包)与 Plugin(插件)的区别详解

Flutter-OH 核心概念&#xff1a;Package&#xff08;包&#xff09;与 Plugin&#xff08;插件&#xff09;的区别详解 欢迎大家 加入跨平台开发者社区。 核心区别详解 首先要明确&#xff1a;Flutter-OH的Plugin是一种特殊的Package&#xff0c;但两者的核心差异在于是否涉…

作者头像 李华
网站建设 2026/5/11 14:56:38

低查重AI教材编写指南!让AI成为你教材创作的得力助手!

编写教材的挑战与AI工具解决方案 在编写教材时&#xff0c;如何精确地满足不同需求确实是一大挑战。不同年龄段的学生在认知能力上差异明显&#xff0c;教材内容的深度和浅显程度都需要把握得当&#xff1b;课堂教学与自主学习的环境需求也各不相同&#xff0c;因此教材的呈现…

作者头像 李华
网站建设 2026/5/12 8:42:41

这片子豆瓣评分连续上涨?观众:还是低了!

董子健第一次当导演&#xff0c;真的让人眼前一亮&#xff01;他的电影《我的朋友安德烈》上映后&#xff0c;口碑一路看涨。最明显的就是豆瓣评分&#xff0c;从最初的6.7分开始&#xff0c;连续涨了两次&#xff0c;现在已经到了6.9分。要知道&#xff0c;在豆瓣上分数想往上…

作者头像 李华
网站建设 2026/5/12 8:42:41

SQL语句性能优化分析及解决方案

前言 应用系统性能测试过程中&#xff0c;性能优化是绕不开的话题&#xff0c;对测试人员而言&#xff0c;性能优化的第一站就是SQL语句的优化与分析。因此本文主要以MySQL数据库为例&#xff0c;介绍常见的慢查询SQL语句执行效率分析与优化方法和简单示例&#xff0c;为致力于…

作者头像 李华
网站建设 2026/5/9 10:50:25

leetcode 929. Unique Email Addresses 独特的电子邮件地址

Problem: 929. Unique Email Addresses 独特的电子邮件地址 依次检查是否存在&#xff0c;若存在分割字符串&#xff0c;后缀检查长度>4且最后存在.com&#xff0c;前缀去掉和后续&#xff0c;去掉’.&#xff0c;检查前缀长度>0 Code class Solution { public:int num…

作者头像 李华