【Spring事务】Spring事务注解 @Transactional 完整体系:从 MySQL 隔离级别到 MyBatis 原理详解
作者大家好我是 CodeStats。一个在底层技术上“考古”了四年的硬核爱好者也是 WWAIC全周项目AI编程范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架从 IoC 容器到嵌入式 Tomcat代码全开源也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。我的技术信条所有高深的技术最后都能用大白话讲清楚。如果讲不清楚说明还没真正理解。一、本文你将获得什么通过本文我将用“层层递进”的方式带你系统掌握 Spring 声明式事务的底层全貌MySQL事务隔离级别四种隔离级别的区别与底层实现MVCC 与锁Transactional 九个参数每个参数的作用、默认值与生产环境建议超时时间timeout到底什么时候抛异常应用层还是数据库层“加入”与“挂起”深入线程上下文彻底理解事务传播的本质MyBatis 两阶段注册 BeanSqlSessionFactory 和 MapperFactoryBean 的类结构体与职责Service 层控制 MyBatis 事务注解在 Service代理在 Mapper中间只隔着一个 ThreadLocal二、正文层层递进问题一MySQL事务四种隔离级别区别是什么本章核心总结隔离级别就是数据库在“性能”和“数据一致性”之间做权衡的四档开关。2.1.1 为什么要隔离多个事务并发执行时如果不加任何控制会出现三类数据异常异常类型表现本质脏读读到其他事务未提交的数据信任了不该信任的中间状态不可重复读同一事务两次读同一行结果不同数据被其他已提交事务修改了幻读同一事务两次范围查询记录数不同其他事务插入了新数据2.1.2 MySQL 四种隔离级别隔离级别脏读不可重复读幻读MySQL 实现方式READ UNCOMMITTED✅ 可能✅ 可能✅ 可能不加锁直接读最新版本READ COMMITTED❌✅ 可能✅ 可能MVCC每次读生成新视图REPEATABLE READ默认❌❌❌ 理论上可能MySQL 通过 MVCC 解决MVCC事务开始生成视图 间隙锁SERIALIZABLE❌❌❌读加共享锁S锁写加排他锁X锁面试追问MySQL 默认 REPEATABLE READ为什么 Oracle 默认 READ COMMITTED因为 MySQL 通过 MVCC 间隙锁在 RR 下解决了幻读但代价是更高的锁开销Oracle 的 RR 不解决幻读通过序列号快照本质上更接近 MySQL 的 RC。2.1.3 Spring 隔离级别枚举javapublic enum Isolation { DEFAULT, // 随数据库MySQLREPEATABLE_READOracleREAD_COMMITTED READ_UNCOMMITTED, READ_COMMITTED, // 生产最推荐避免脏读且性能优秀 REPEATABLE_READ, SERIALIZABLE }问题二Spring的 Transactional 注解九个参数分别什么作用本章核心总结九大参数本质是开发者在方法上贴的一张“事务执行命令清单”告诉 Spring“怎么开、怎么关、什么情况算失败”。2.2.1 参数全览参数类型默认值一句话作用value / transactionManagerString多数据源时指定用哪个事务管理器propagationPropagationREQUIRED有事务就加入没事务就新建isolationIsolationDEFAULT使用数据库默认隔离级别timeoutint秒-1无限事务最多跑多久超时就抛异常timeoutStringString-timeout 的字符串版readOnlybooleanfalse是否为只读事务优化 flushrollbackForClass[]{}哪些异常必须回滚rollbackForClassNameString[]{}同上用类名字符串noRollbackForClass[]{}哪些异常不回滚2.2.2 核心参数详解1propagation —— 7种传播行为面试高频传播行为有事务时的行为无事务时的行为经典场景REQUIRED默认加入新建增删改业务SUPPORTS加入非事务执行查询操作有也行没有更快MANDATORY加入抛异常强制要求在事务中执行REQUIRES_NEW挂起外部新建独立事务新建操作日志必须独立提交NOT_SUPPORTED挂起外部非事务执行非事务执行执行不重要的清理脚本NEVER抛异常非事务执行绝对禁止事务的场景NESTED在当前事务中创建保存点新建同REQUIRED局部回滚外部统一提交2rollbackFor —— 最关键的生产配置java// 生产环境推荐只要抛异常就回滚不管是不是RuntimeException Transactional(rollbackFor Exception.class) public void transfer() { ... }原因Spring 默认只对RuntimeException和Error回滚对SQLException、IOException等checked 异常不回滚。不写rollbackFor Exception.class数据库报错可能就悄悄吞掉了。问题三Transactional 超时时间是到时间就抛异常还是执行时检查是应用层还是数据库超时本章核心总结Transactional 的超时是“执行时检查”而非“定时炸弹”它只在数据库操作前看一眼秒表超时就报错。2.3.1 检查机制执行时检查非后台轮询答案是执行时检查。Spring 不会启动一个后台线程每秒钟去扫描超时事务而是在数据库操作执行前进行“按需检查”。检查点有三个每次获取数据库连接时最核心DataSourceUtils.getConnection()内部会检查ConnectionHolder.isTimeout()。事务提交/回滚时兜底检查防止纯逻辑执行太久。事务开始时启动一个定时任务仅用于标记超时状态配合检查点使用。2.3.2 超时计时范围Spring 事务超时 事务开始时间 到 所有数据库操作执行完成的时间。致命陷阱如果方法里有Thread.sleep(15000)纯 Java 逻辑且 timeout10不会触发超时。因为 Spring 只有在执行 SQL 时才去检查秒表sleep期间没有数据库操作不触发检查。2.3.3 应用层超时 vs 数据库超时层级实现类作用范围抛出异常应用层SpringConnectionHolder.timeout整个事务方法内的所有SQLTransactionTimedOutException数据库层JDBCStatement.setQueryTimeout()单条 SQL 语句的执行时间SQLTimeoutException两者是独立的Spring 超时到时间了会在下一次数据库操作前抛TransactionTimedOutException。数据库超时是单条 SQL 执行太久被数据库 kill 掉。问题四Transactional“加入”和“挂起”是什么意思本章核心总结“加入”就是共享同一根数据库连接“挂起”就是先把旧连接寄存起来腾出手来干自己的事。2.4.1 “加入”Join—— 共享同一连接当前线程的ThreadLocal实际是TransactionSynchronizationManager里已经有绑定的连接时新方法直接取出来用不新建。java// 加入的本质直接复用 Connection existing TransactionSynchronizationManager.getResource(dataSource); if (existing ! null) { // 这就是加入直接使用现有连接共用一个事务上下文 return existing; }后果所有加入的方法共享同一个物理连接最终一起commit或一起rollback。2.4.2 “挂起”Suspend—— 寄存旧连接换新连接当传播行为是REQUIRES_NEW或NOT_SUPPORTED时Spring 执行挂起java// 挂起的本质存旧换新 Connection old TransactionSynchronizationManager.unbindResource(dataSource); // 取出并解绑 suspendedResources old; // 存到栈里 Connection newConn dataSource.getConnection(); // 建立新连接 bindResource(dataSource, newConn); // 绑定新连接 // ... 执行内部方法 ... // 内部方法结束后恢复旧连接 bindResource(dataSource, old);后果内外两个事务完全独立内部提交/回滚不影响外部。2.4.3 REQUIRES_NEW vs NESTED终极对比维度REQUIRES_NEWNESTED连接数2个挂起旧新建新1个复用旧内部回滚内部回滚后立即提交如果成功回滚到 Savepoint不提交外部回滚不影响内部已独立提交内部一起被回滚本质两个独立物理事务一个物理事务 一个逻辑保存点问题五MyBatis 两阶段注册 Bean 完整类结构体——SqlSessionFactory 和 MapperFactoryBean 作用本章核心总结MyBatis 启动阶段就是两件事——把 SQL 编译成“蓝图”MappedStatement把 Mapper 接口包装成“工厂”MapperFactoryBean。2.5.1 第一阶段启动注册IoC 容器构建Spring Boot 启动时MybatisAutoConfiguration负责向容器注册两个核心 Beantext【阶段一BeanDefinition 注册】 1. MybatisAutoConfiguration 被 Conditional 条件触发 2. 创建 SqlSessionFactoryBean这是一个 FactoryBean └── 解析 mybatis-config.xml 和所有 Mapper.xml └── 生成 Configuration 对象内含所有 MappedStatement 3. 通过 MapperScan 扫描所有 Mapper 接口 └── 为每个 Mapper 接口生成 MapperFactoryBean 的 BeanDefinition核心类结构体java// 1. SqlSessionFactory实际实现是 DefaultSqlSessionFactory public class DefaultSqlSessionFactory implements SqlSessionFactory { // 持有核心配置对象 private final Configuration configuration; Override public SqlSession openSession() { // 从 configuration 中取 Environment获取 DataSource // 创建 Executor 和 DefaultSqlSession } } // Configuration 内部结构第一阶段解析成果 public class Configuration { // 所有 SQL 的蓝图 protected final MapString, MappedStatement mappedStatements; // 所有注册的 Mapper 接口 protected final MapClass?, MapperRegistry mapperRegistry; // 数据源、事务工厂等 protected Environment environment; }java// 2. MapperFactoryBean为每个 Mapper 接口生成代理 public class MapperFactoryBeanT extends SqlSessionDaoSupport implements FactoryBeanT { private ClassT mapperInterface; Override public T getObject() throws Exception { // 返回动态代理对象不是 Mapper 实现类 return getSqlSession().getMapper(this.mapperInterface); } }2.5.2 第二阶段运行时调用执行 SQLtext【阶段二运行时调用链】 Service 调用 mapper.selectById() ↓ MapperProxyJDK动态代理触发 invoke() ↓ MapperMethod.execute() 根据 SQL 类型路由 ↓ SqlSessionTemplateSpring包装的 SqlSession ↓ 从 Configuration 中取出 MappedStatement蓝图 ↓ Executor 执行Simple / Reuse / Batch ↓ StatementHandler 编译 SQL 设置参数 ↓ ResultSetHandler 映射结果问题六Service 层的 Transactional 如何控制 MyBatis 的事务本章核心总结Service 层通过 ThreadLocal 这个“线程公共储物柜”把连接交给 Mapper实现事务的隔空控制。2.6.1 核心答案隔空传物ThreadLocalService 和 Mapper 之间没有任何直接引用关系。Service 的代理把连接放进当前线程的ThreadLocal仓库Mapper 执行时主动去这个仓库里拿。2.6.2 完整调用链路含源码级原理步骤执行主体具体动作对 ThreadLocal 做了什么①TransactionInterceptorAOP代理进入 Service 方法前从 DataSource 获取物理连接执行setAutoCommit(false)存入TransactionSynchronizationManager.bindResource(dataSource, connection)②UserService真实业务对象执行orderDao.insert()无感不关心连接③MapperProxyJDK代理触发invoke()委托给SqlSessionTemplate无感④SqlSessionTemplate内部拦截器调用DataSourceUtils.getConnection(dataSource)取出从TransactionSynchronizationManager.getResource(dataSource)取出第①步放入的连接⑤Executor用取出的连接执行 JDBC 操作无感⑥TransactionInterceptor后置方法返回后根据是否抛异常执行commit()或rollback()移除TransactionSynchronizationManager.unbindResource(dataSource)2.6.3 为什么必须把 Transactional 加在 Service 层如果加在 Mapper 层每次调用userMapper.insert()就开启一个独立事务执行完立即提交。Service 里三个 Mapper 操作下单、扣库存、扣余额就是三个独立事务。第三个报错时前两个已经提交了无法回滚数据产生不一致。如果加在 Service 层整个 Service 方法是一个事务边界。所有 Mapper 操作共享同一个连接一起提交或一起回滚。保证业务原子性。2.6.4 Spring MyBatis 整合的连接获取源码级逻辑java// SqlSessionTemplate 内部获取连接的核心逻辑简化 public Connection getConnection(DataSource dataSource) { // 1. 先去当前线程的 ThreadLocal 里找事务管理器放连接的地方 ConnectionHolder holder TransactionSynchronizationManager.getResource(dataSource); if (holder ! null holder.hasConnection()) { // 2. 找到了说明当前方法被 Transactional 包裹 // 直接返回这个已经开启事务的连接这就是“加入”当前事务 return holder.getConnection(); } else { // 3. 没找到说明没有事务 // 直接从数据源 new 一个物理连接非事务场景用完立即关闭 return dataSource.getConnection(); } }三、全文内容总结层层递进的逻辑链条我们把六个问题串起来形成一条完整的技术认知链层级核心问题核心结论数据库层问题一MySQL 隔离级别怎么选隔离级别是“性能”与“一致性”的四档开关READ_COMMITTED是生产最佳平衡点。Spring API 层问题二九个参数怎么配九大参数是贴在方法上的“事务执行命令清单”rollbackForException.class是必加项。Spring 内部机制问题三、四超时和传播怎么实现超时是“执行时检查”而非定时炸弹加入/挂起本质是 ThreadLocal 中连接的“共享”与“寄存”。MyBatis 整合层问题五MyBatis 怎么注册到 Spring启动阶段把 SQL 编译成MappedStatement蓝图把 Mapper 包装成MapperFactoryBean工厂。运行时线程层问题六Service 怎么控制 Mapper通过TransactionSynchronizationManagerThreadLocal 仓库完成连接的隔空传递与边界控制。终极结论Spring 事务的本质从来不是代码块嵌套而是数据库连接Connection在不同层级之间有序流转与边界控制。理解了ThreadLocal这个“线程公共储物柜”你就理解了 Spring 事务、MyBatis 整合、乃至整个 Java Web 声明式治理的底层灵魂。四、最后如果这篇从数据库隔离级别一路“考古”到 ThreadLocal 底层流转的文章对你有帮助欢迎点赞 —— 让更多朋友看到硬核内容收藏⭐ —— 随时回顾 Spring MyBatis 事务全链路关注 —— 下次我讲“手写 Spring 事务管理器实现”时第一时间收到有任何疑问欢迎评论区交流我会从源码层面一一回复。

相关新闻

为什么CodeCombat是学习编程的终极游戏化平台?

为什么CodeCombat是学习编程的终极游戏化平台?

为什么CodeCombat是学习编程的终极游戏化平台? 【免费下载链接】codecombat Game for learning how to code. 项目地址: https://gitcode.com/gh_mirrors/co/codecombat 还在为枯燥的编程语法而头疼吗?还在对着黑白代码编辑器感到无趣吗&#xff…

2026/10/18 6:26:53 阅读更多 →
终极图像清理指南:如何使用AntiDupl.NET三步智能删除重复照片

终极图像清理指南:如何使用AntiDupl.NET三步智能删除重复照片

终极图像清理指南:如何使用AntiDupl.NET三步智能删除重复照片 【免费下载链接】AntiDupl A program to search similar and defect pictures on the disk 项目地址: https://gitcode.com/gh_mirrors/an/AntiDupl 你是否曾因电脑中堆积如山的重复照片而烦恼&a…

2026/10/18 6:26:55 阅读更多 →
终极指南:如何完整导出安卓微信聊天记录并实现数据分析

终极指南:如何完整导出安卓微信聊天记录并实现数据分析

终极指南:如何完整导出安卓微信聊天记录并实现数据分析 【免费下载链接】wechat-dump Analyzing your wechat message history from android 项目地址: https://gitcode.com/gh_mirrors/we/wechat-dump 微信作为中国最流行的即时通讯应用,其封闭的…

2026/10/18 6:26:59 阅读更多 →

最新新闻

高光谱图像处理工具包:PCA降维+KNN/CNN分类全流程实战

高光谱图像处理工具包:PCA降维+KNN/CNN分类全流程实战

简介:一套面向高光谱图像处理的 MATLAB 工具包,整合了主成分分析、K近邻分类与卷积神经网络三类主流算法,适用于遥感、环境保护、地质勘探、农业监测等领域的科研人员与工程师,用于解决高光谱数据维度高、信息冗余、传统分类方法难…

2026/10/18 9:49:35 阅读更多 →
智能体交互实战:从工具调用协议到上下文状态管理

智能体交互实战:从工具调用协议到上下文状态管理

你有没有遇到过这种情况:跟一个智能体聊着聊着,它突然忘了自己十分钟前说过的话,或者更离谱——它明明可以调用工具,却一本正经地编造了一个并不存在的天气数据给你。我最早做智能体交互项目时,以为这东西无非就是把大…

2026/10/18 9:49:35 阅读更多 →
告别 Copilot?Codex 本地化部署指南:从环境搭建到私有化实践

告别 Copilot?Codex 本地化部署指南:从环境搭建到私有化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 9:49:35 阅读更多 →
一行命令解锁 Claude Code 全部功能:用 TaoToken 统一 Key 打通 AI 编程链路

一行命令解锁 Claude Code 全部功能:用 TaoToken 统一 Key 打通 AI 编程链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 9:49:35 阅读更多 →
华三三层端口聚合:静态与动态聚合原理及故障排查

华三三层端口聚合:静态与动态聚合原理及故障排查

简介:本资源是一份面向网络工程师、运维人员及备考H3C认证的技术人员的实操型配置指南,聚焦华三交换机三层端口聚合的核心场景——静态与动态两种模式的完整部署方案。内容覆盖聚合口创建、三层路由模式启用、物理端口加入、动态协商配置及关键验证命令&…

2026/10/18 9:49:35 阅读更多 →
MiniCPM-o-4.5全模态模型在OpenVINO上的边缘部署实践

MiniCPM-o-4.5全模态模型在OpenVINO上的边缘部署实践

1. 为什么把 MiniCPM-o-4.5 托付给 OpenVINO先交代一下背景。去年底我们接了一个离线设备端的智能问答需求,要求一台不带独立显卡的工控机完成文本问答、图像理解、音频转写三类任务。选型比来比去,最终敲定了 MiniCPM-o-4.5 这个全模态模型,…

2026/10/18 9:48:35 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →