当前位置: 首页 > news >正文

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 对象所有关于事务的切面列表,并将匹配成功的事务属性保存到缓存;
  • 从缓存取出事务属性,然后创建、启动事务,执行业务逻辑,最后提交或者回滚事务。
http://www.cnnetsun.cn/news/1806898.html

相关文章:

  • “INMS: Memory Sharing for Large Language Model based Agents“ 论文笔记秦
  • 别再只画折线图了!用Cartopy和EOF分析为你的气象数据制作酷炫时空模态图
  • RabbitMq(五) -- SpringBoot整合 RabbitMQ 完整实现
  • Nginx 学习总结统
  • Qwen3-0.6B-FP8实操手册:从llm.log日志验证到首次提问成功全记录
  • AI原生研发供应商白名单动态评估模型(V2.3):融合GitHub Star增速衰减率、Hugging Face Model Hub复用深度、国产算力栈兼容熵值——仅开放至Q3末
  • 从代码提交到模型上线:SITS2026定义的8个AI原生CI/CD必检关卡(含GitOps集成模板下载)
  • React vs Vue 在 AI 流式对话场景。目前AI交互页面,哪些用的react,哪些用的vue?
  • NTFS2BTRFS 技术深度解析:从Windows文件系统到Linux存储的革命性转换
  • 倍莱鲜羊奶商城软件源码开发
  • OpenClaw隐私方案:断网环境下运行Qwen2.5-VL-7B处理机密文件
  • Kafka-King:现代化Kafka管理GUI工具的技术解析与使用指南
  • 探索三菱FX3U源码及相关生产方案
  • ESP32内部存储实战:Flash-EEPROM高效数据掉电保存技巧
  • 没钱没设备?STM32入门不用买板!纯仿真0成本学习攻略|系列第1篇
  • 分钟搞懂深度学习AI:实操篇:Attention葡
  • 10款答辩必备AI工具推荐,aibiye在内,附模板使用心得分享。
  • Python Scrcpy Client实战解析:构建高效的Android设备远程控制方案
  • Go赋值操作的关键细节
  • 如何为波斯语项目选择完美的开源字体?Behdad字体深度解析与实战指南
  • Java生产者消费者模式实战解析
  • 别再数据线了!用FastAPI 分钟搭个局域网文件+剪贴板神器谓
  • 冷库维护上门服务全攻略:这些疑问你肯定也有
  • Speechless:终极微博备份指南 - 如何3分钟将微博内容安全导出为PDF
  • AlmaLinux构建LNMP
  • SITS2026架构白皮书解密:为什么92%的传统Serverless团队将在2026年前被迫重构?
  • DLT645-2007协议常见报文错误排查指南:从校验失败到数据域乱码
  • 算法·贪心
  • AI原生DevOps流水线重构(奇点大会闭门报告节选):CI/CD→AI/CD的8项指标迁移清单
  • 揭秘2026奇点智能技术大会核心成果:如何用AI原生审查引擎将PR平均审核时长从47分钟压缩至93秒?