网站开发所需资料,河北住房和城乡建设厅网站官网,wordpress 二次元,企业招聘官网目录
Spring事务和事务传播机制#xff1a;新手超详细指南
一、事务核心概念#xff08;代码前的准备知识#xff09;
1.1 什么是事务#xff1f;#xff08;代码体现版#xff09;
1.2 为什么需要事务#xff1f;#xff08;代码场景#xff09;
二、Spring中事…目录Spring事务和事务传播机制新手超详细指南一、事务核心概念代码前的准备知识1.1 什么是事务代码体现版1.2 为什么需要事务代码场景二、Spring中事务的实现方式2.1 编程式事务详细注释版2.2 声明式事务 Transactional推荐方式三、Transactional 注解详解3.1 rollbackFor 属性异常回滚详解3.2 事务隔离级别3.2.1 MySQL隔离级别回顾用代码理解3.2.2 Spring设置隔离级别3.3 事务传播机制重头戏3.3.1 什么是事务传播机制3.3.2 7种传播机制全览3.3.3 代码演示完整带注释版3.3.3.1 REQUIRED加入事务3.3.3.2 REQUIRES_NEW新建事务3.3.3.3 NEVER不支持事务3.3.3.4 NESTED嵌套事务3.3.3.5 NESTED vs REQUIRED局部回滚四、完整项目代码超详细注释版4.1 pom.xml依赖详细注释4.2 application.properties配置详细注释4.3 实体类详细注释4.4 Mapper接口详细注释4.5 Service层详细注释4.6 Controller层详细注释4.7 数据库初始化脚本详细注释五、总结与代码对应关系5.1 Spring事务实现方式对比5.2 Transactional关键属性代码体现5.3 事务传播机制总结表六、常见问题与代码解答Q1为什么要在Service层而不是Controller层使用事务Q2NESTED和REQUIRED有什么区别Q3如何在事务中捕获异常并实现部分回滚七、扩展知识点7.1 事务ACID特性的代码体现7.2 事务实现原理AOP代理八、最佳实践代码模板最后的话八、Spring事务失效场景详解新手必看8.1 同类自调用问题最坑问题描述错误代码示例失效原因分析正确解决方案8.2 方法修饰符非public问题描述错误代码示例正确解决方案8.3 方法是final/static问题描述错误代码示例正确解决方案8.4 多线程调用问题问题描述错误代码示例正确解决方案8.5 异常被catch未抛出问题描述错误代码示例正确解决方案8.6 数据库引擎不支持事务问题描述问题代码SQL层面正确解决方案8.7 未配置事务管理器问题描述错误配置Spring Boot正确配置8.8 传播机制设置错误问题描述错误代码示例正确解决方案8.9 总结事务失效检查清单Spring事务和事务传播机制新手超详细指南我会为你重新梳理整个Spring事务知识体系重点补充代码与知识点的对应关系并对所有代码进行逐行详细注释。一、事务核心概念代码前的准备知识1.1 什么是事务代码体现版事务就是一组要么一起成功要么一起失败的数据库操作。来看一个没有事务的灾难场景// 没有事务的转账方法危险 public void transferMoney(String fromAccount, String toAccount, int amount) { // 第一步A账户扣钱 jdbcTemplate.update(UPDATE account SET balance balance - ? WHERE user_id ?, amount, fromAccount); // 第二步B账户加钱 // 如果这里发生异常比如网络断开A的钱已经扣了但B没收到 int i 10 / 0; // 模拟异常 jdbcTemplate.update(UPDATE account SET balance balance ? WHERE user_id ?, amount, toAccount); }对应知识点这个例子说明如果没有事务保护两个操作之间发生异常会导致数据不一致。A账户的钱凭空消失。1.2 为什么需要事务代码场景看三个真实业务场景场景1转账操作上面已演示场景2秒杀系统// 没有事务的秒杀方法会导致超卖 public void seckill(String productId) { // 第一步创建订单成功 orderMapper.createOrder(productId, userId); // 第二步扣减库存失败 // 如果库存扣减失败但订单已创建就超卖了 stockMapper.reduceStock(productId); // 假设这里抛异常 }场景3订单创建// 没有事务的订单创建数据不完整 public void createOrder(Order order) { // 主订单表插入成功 orderMapper.insertMainOrder(order); // 订单明细表插入失败比如某个明细数据格式错误 orderMapper.insertOrderItems(order.getItems()); // 抛出异常 // 库存更新没执行 // 结果数据库中只有主订单没有明细数据不完整 }二、Spring中事务的实现方式2.1 编程式事务详细注释版这是最原始但最灵活的方式适合需要精细控制事务的场景。// 1. 导入Spring事务管理核心类 // Spring提供的JDBC事务管理器负责实际的事务操作开启、提交、回滚 import org.springframework.jdbc.datasource.DataSourceTransactionManager; // 事务定义接口用于设置事务属性隔离级别、传播行为、超时时间等 import org.springframework.transaction.TransactionDefinition; // 事务状态接口表示当前事务的状态用于提交或回滚 import org.springframework.transaction.TransactionStatus; // Spring提供的模板类简化编程式事务的写法后面会介绍 import org.springframework.transaction.support.TransactionTemplate; // Spring MVC注解标识这是一个处理HTTP请求的控制器 import org.springframework.web.bind.annotation.RequestMapping; // Spring MVC注解标识控制器返回的数据直接写入HTTP响应体RESTful风格 import org.springframework.web.bind.annotation.RestController; // Spring依赖注入注解自动将Spring容器中的Bean注入到字段 import org.springframework.beans.factory.annotation.Autowired; // 业务服务类处理用户注册逻辑 import com.example.demo.service.UserService; /** * 用户注册控制器 - 演示编程式事务 * 这是最原始的事务管理方式手动控制事务的每一个步骤 */ RequestMapping(/user) // 设置该控制器的基础请求路径为/user RestController // 声明这是一个RESTful控制器所有方法返回JSON数据 public class UserController { // 2. 注入事务管理器核心组件 // DataSourceTransactionManager是Spring提供的JDBC事务管理器 // 它负责获取数据库连接、开启事务、提交事务、回滚事务 Autowired private DataSourceTransactionManager dataSourceTransactionManager; // 3. 注入事务定义设置事务属性 // TransactionDefinition用于定义事务的行为特性 // 比如隔离级别、传播行为、超时时间、是否只读等 Autowired private TransactionDefinition transactionDefinition; // 4. 注入用户服务执行业务逻辑 // UserService包含具体的业务操作如插入用户数据 Autowired private UserService userService; /** * 用户注册接口 - 使用编程式事务 * param name 用户名 * param password 密码 * return 注册结果 */ RequestMapping(/registry) // 映射HTTP请求路径为/user/registry public String registry(String name, String password) { // 5. 【核心】开启事务 // getTransaction()方法根据transactionDefinition的定义开启一个新事务 // 返回TransactionStatus对象用于追踪当前事务状态 // 相当于执行SQL: START TRANSACTION; TransactionStatus transactionStatus dataSourceTransactionManager.getTransaction(transactionDefinition); try { // 6. 在事务中执行业务逻辑 // 如果这里抛出RuntimeException事务会自动回滚 userService.registryUser(name, password); // 7. 【核心】提交事务 // 如果try块中的所有操作都成功提交事务 // 相当于执行SQL: COMMIT; // 所有对数据库的修改永久生效 dataSourceTransactionManager.commit(transactionStatus); return 注册成功; } catch (Exception e) { // 8. 【核心】回滚事务 // 如果try块中任何代码抛出异常捕获后回滚事务 // 相当于执行SQL: ROLLBACK; // 所有在事务中的数据库操作都撤销就像从未执行过 dataSourceTransactionManager.rollback(transactionStatus); // 重新抛出异常让Spring框架处理比如返回500错误 throw e; } } }知识点与代码的对应关系知识点代码体现关键行开启事务getTransaction()第55行提交事务commit()第65行回滚事务rollback()第72行事务属性设置TransactionDefinition第31行事务状态追踪TransactionStatus第55行2.2 声明式事务Transactional推荐方式这是实际开发中最常用的方式通过注解自动管理事务。第一步添加依赖pom.xml!-- Spring事务管理依赖 -- dependency groupIdorg.springframework/groupId artifactIdspring-tx/artifactId /dependency第二步使用注解// 导入Spring事务注解 // 声明式事务的核心注解加在方法或类上自动开启事务管理 import org.springframework.transaction.annotation.Transactional; // 导入其他必要类 import org.springframework.beans.factory.annotation.Autowired; // 依赖注入 import org.springframework.web.bind.annotation.RequestMapping; // 请求映射 import org.springframework.web.bind.annotation.RestController; // REST控制器 /** * 用户注册控制器 - 演示声明式事务 * 这种方式无需手动编写事务管理代码Spring通过AOP自动代理实现 */ RequestMapping(/user) // 基础请求路径 RestController // RESTful控制器 public class TransactionalController { // 注入用户服务 Autowired private UserService userService; /** * 用户注册接口 - 声明式事务 * * Transactional注解的作用 * 1. 在方法执行前自动开启事务相当于getTransaction() * 2. 在方法成功执行后自动提交事务相当于commit() * 3. 在方法抛出未捕获异常时自动回滚事务相当于rollback() * * 注意默认只对RuntimeException和Error回滚 */ Transactional // 核心注解加在public方法上才有效 RequestMapping(/registry) // 映射请求路径 public String registry(String name, String password) { // Spring会在调用此方法前自动开启事务 // 方法内的所有数据库操作都在同一个事务中 userService.registryUser(name, password); // 插入用户信息 // 如果这里抛出RuntimeException整个事务会自动回滚 // 模拟异常int i 10/0; return 注册成功; // 方法正常返回Spring自动提交事务 } }代码与知识点的桥梁// 编程式事务的繁琐写法 public void oldWay() { TransactionStatus status manager.getTransaction(definition); // 手动开启 try { // 业务代码 manager.commit(status); // 手动提交 } catch(Exception e) { manager.rollback(status); // 手动回滚 throw e; } } // 声明式事务的简洁写法Spring AOP自动代理 Transactional // 一个注解替代所有手动代码 public void newWay() { // 业务代码 // Spring自动处理开启、提交、回滚 }三、Transactional注解详解3.1rollbackFor属性异常回滚详解问题演示代码import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; import import java.io.IOException; // 导入IO异常类检查型异常 /** * 演示默认回滚机制的缺陷 */ RestController public class RollbackDemoController { /** * 默认事务配置 * 问题当抛出IOException检查型异常时事务不会回滚 * 原因Spring默认只对RuntimeException和Error回滚 */ Transactional // 没有指定rollbackFor RequestMapping(/r2) public String r2(String name, String password) throws IOException { userService.registryUser(name, password); // 插入用户成功 log.info(用户数据插入成功); // 抛出检查型异常IOException不是RuntimeException // Spring会认为这不是我需要管的异常所以不会回滚事务 if (true) { throw new IOException(网络异常); // 数据库操作不会回滚 } return r2; } /** * 正确的事务配置 * 解决方案通过rollbackFor指定需要回滚的异常类型 */ Transactional( rollbackFor Exception.class // 指定所有Exception都回滚 // rollbackFor {IOException.class, SQLException.class} // 也可以指定多个 ) RequestMapping(/r3) public String r3(String name, String password) throws IOException { userService.registryUser(name, password); log.info(用户数据插入成功); // 现在抛出IOException也会回滚了 if (true) { throw new IOException(网络异常); } return r3; // 这行不会执行因为上面抛异常了 } }结论与代码的对应关系默认行为代码体现结果只回滚RuntimeExceptionTransactional无参数IOException不会回滚回滚所有异常Transactional(rollbackFor Exception.class)所有异常都会回滚3.2 事务隔离级别3.2.1 MySQL隔离级别回顾用代码理解-- 查询当前MySQL隔离级别 SELECT global.tx_isolation, tx_isolation; -- 结果示例REPEATABLE-READMySQL默认3.2.2 Spring设置隔离级别import org.springframework.transaction.annotation.Isolation; // 导入隔离级别枚举 import org.springframework.transaction.annotation.Transactional; /** * 隔离级别演示 */ RestController public class IsolationDemoController { /** * 设置隔离级别为READ_COMMITTED * * 解决脏读问题读取到未提交的数据 * 场景适合读多写少的场景提高并发性能 * * 代码中的体现 * Transactional(isolation ...) 指定隔离级别 */ Transactional( isolation Isolation.READ_COMMITTED // 读已提交 ) RequestMapping(/r3) public String r3(String name, String password) { // 在这个事务中只能读取到其他事务已提交的数据 // 避免了脏读但可能出现不可重复读 return r3; } /** * 使用数据库默认隔离级别 * * Isolation.DEFAULT -1表示跟随数据库默认设置 * MySQL下等效于Isolation.REPEATABLE_READ */ Transactional( isolation Isolation.DEFAULT // 使用数据库默认推荐 ) RequestMapping(/r4) public String r4(String name, String password) { return r4; } }知识点与代码的直接对应// 隔离级别在代码中的体现就是这一个参数 Transactional( isolation Isolation.READ_UNCOMMITTED // 读未提交最低级别可能脏读 // isolation Isolation.READ_COMMITTED // 读已提交避免脏读 // isolation Isolation.REPEATABLE_READ // 可重复读避免不可重复读MySQL默认 // isolation Isolation.SERIALIZABLE // 串行化最高级别避免幻读但性能差 )3.3 事务传播机制重头戏3.3.1 什么是事务传播机制场景代码// 主方法带事务 Transactional public void methodA() { // 调用另一个带事务的方法 serviceB.methodB(); // 问题B是加入A的事务还是创建自己的事务 }代码体现传播机制就是解决methodB该如何处理事务的问题。3.3.2 7种传播机制全览// Spring源码中的Propagation枚举 public enum Propagation { REQUIRED(0), // 默认值如果存在事务则加入否则新建 SUPPORTS(1), // 如果存在事务则加入否则非事务方式运行 MANDATORY(2), // 强制要求存在事务否则抛异常 REQUIRES_NEW(3), // 总是新建事务挂起当前事务 NOT_SUPPORTED(4), // 以非事务方式运行挂起当前事务 NEVER(5), // 以非事务方式运行如果存在事务则抛异常 NESTED(6); // 如果存在事务则创建嵌套事务 }3.3.3 代码演示完整带注释版场景说明注册用户信息 记录日志两个操作都需要事务但日志记录失败不应该影响用户注册。3.3.3.1REQUIRED加入事务// Controller层 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 事务传播机制测试控制器 * 场景用户注册时记录日志 */ RequestMapping(/propaga) // 基础路径 RestController // REST控制器 public class PropagationController { // 注入用户服务 Autowired private UserService userService; // 注入日志服务 Autowired private LogService logService; /** * 测试REQUIRED传播机制 * * REQUIRED的行为 * 1. 如果methodA()调用methodB()时A已经有事务 * 2. 那么B会加入A的事务不会创建新事务 * 3. 整个事务要么全部成功要么全部失败 * * 代码体现 * Transactional(propagation Propagation.REQUIRED) */ Transactional(propagation Propagation.REQUIRED) // 开启主事务 RequestMapping(/p1) public String p1(String name, String password) { // 1. 注册用户加入当前事务 userService.registryUser(name, password); // 使用主事务 // 2. 记录日志加入当前事务 logService.insertLog(name, 用户注册); // 使用主事务 // 3. 如果insertLog()抛出异常整个事务包括registryUser都会回滚 return p1; } } // UserService层 import com.example.demo.mapper.UserInfoMapper; // 用户Mapper import lombok.extern.slf4j.Slf4j; // 日志注解 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // 服务层注解 import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * 用户服务类 * 负责用户相关的业务逻辑 */ Slf4j // 自动生成日志对象log用于打印日志 Service // 声明这是一个服务层组件Spring会自动扫描并注册为Bean public class UserService { // 注入用户Mapper操作数据库 Autowired private UserInfoMapper userInfoMapper; /** * 注册用户方法 * * 传播机制REQUIRED加入当前事务 * 行为如果调用者已经开启事务就加入该事务否则新建 * * 代码体现 * Transactional(propagation Propagation.REQUIRED) */ Transactional(propagation Propagation.REQUIRED) public void registryUser(String name, String password) { // 插入用户信息到数据库 // 这个方法没有自己的事务它加入调用者的事务 userInfoMapper.insert(name, password); log.info(用户数据插入成功); // 记录成功日志 } } // LogService层 import com.example.demo.mapper.LogInfoMapper; // 日志Mapper import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * 日志服务类 * 负责记录操作日志 */ Slf4j Service public class LogService { // 注入日志Mapper Autowired private LogInfoMapper logInfoMapper; /** * 插入日志方法 * * 传播机制REQUIRED加入当前事务 * 问题如果这里抛出异常整个事务都会回滚 * 包括已经成功的registryUser() * * 代码体现在方法中主动抛出异常 */ Transactional(propagation Propagation.REQUIRED) public void insertLog(String name, String op) { // 模拟异常除零错误RuntimeException // 这会导致整个事务回滚包括用户注册信息 int a 10 / 0; // ❌ 抛出ArithmeticException // 这行代码不会执行因为上面已经抛异常 logInfoMapper.insertLog(name, op); // 记录日志 } }执行结果分析// 调用链 p1() [主事务开始] → registryUser() [加入主事务] → 插入用户成功 → insertLog() [加入主事务] → 抛出异常 → 主事务回滚 → 用户数据也被回滚 // 数据库结果用户表和日志表都**没有**数据 // 原因REQUIRED让所有方法共享同一个事务知识点与代码的对应关系传播机制代码体现执行结果REQUIREDTransactional(propagation Propagation.REQUIRED)所有操作在同一个事务中一荣俱荣一损俱损3.3.3.2REQUIRES_NEW新建事务修改代码// UserService层修改 import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; Slf4j Service public class UserService { Autowired private UserInfoMapper userInfoMapper; /** * 传播机制REQUIRES_NEW新建事务 * 行为总是创建新事务挂起调用者的事务 * * 代码体现 * Transactional(propagation Propagation.REQUIRES_NEW) * * 执行流程 * 1. p1()开启主事务 * 2. registryUser()发现REQUIRES_NEW挂起主事务创建新事务 * 3. 在新事务中执行插入用户操作 * 4. 新事务提交主事务被恢复 * 5. insertLog()抛出异常只回滚自己的事务 */ Transactional(propagation Propagation.REQUIRES_NEW) public void registryUser(String name, String password) { userInfoMapper.insert(name, password); // 在新事务中执行 log.info(用户数据插入成功); } } // LogService层修改 Slf4j Service public class LogService { Autowired private LogInfoMapper logInfoMapper; /** * 传播机制REQUIRES_NEW新建事务 * 行为总是创建新事务挂起调用者的事务 * * 代码体现 * Transactional(propagation Propagation.REQUIRES_NEW) * * 执行流程 * 1. 主事务被挂起 * 2. 创建新的事务执行日志插入 * 3. 抛出异常只回滚当前新事务 * 4. 恢复主事务 */ Transactional(propagation Propagation.REQUIRES_NEW) public void insertLog(String name, String op) { int a 10 / 0; // 在新事务中抛异常 logInfoMapper.insertLog(name, op); } }执行结果分析// 调用链 p1() [主事务开始] → registryUser() [挂起主事务创建新事务] → 插入用户成功 → **新事务提交** → 恢复主事务 → insertLog() [挂起主事务创建新事务] → 抛出异常 → **新事务回滚** → 恢复主事务 → 主事务提交 // 数据库结果用户表**有**数据日志表**没有**数据 // 原因REQUIRES_NEW让每个方法独立事务互不影响知识点与代码的对应关系传播机制代码体现执行结果REQUIRES_NEWTransactional(propagation Propagation.REQUIRES_NEW)方法独立事务成功的方法提交失败的方法单独回滚3.3.3.3NEVER不支持事务Slf4j Service public class UserService { Autowired private UserInfoMapper userInfoMapper; /** * 传播机制NEVER不支持事务 * 行为以非事务方式运行如果调用者存在事务则抛出异常 * * 代码体现 * Transactional(propagation Propagation.NEVER) * * 执行流程 * 1. p1()开启了主事务 * 2. registryUser()被调用但传播机制是NEVER * 3. 由于当前存在事务Spring抛出异常 * Existing transaction found for transaction marked with propagation never * 4. 整个操作失败 */ Transactional(propagation Propagation.NEVER) public void registryUser(String name, String password) { userInfoMapper.insert(name, password); } }执行结果// 抛出异常Existing transaction found for transaction marked with propagation never // NESTED与REQUIRED区别最关键3.3.3.4NESTED嵌套事务// UserService层修改 Slf4j Service public class UserService { Autowired private UserInfoMapper userInfoMapper; /** * 传播机制NESTED嵌套事务 * 行为如果调用者存在事务则创建嵌套事务子事务 * * 代码体现 * Transactional(propagation Propagation.NESTED) * * 技术原理 * 嵌套事务使用数据库的Savepoint保存点实现 * 相当于SAVEPOINT savepoint1; // 创建保存点 * 回滚时ROLLBACK TO SAVEPOINT savepoint1; // 回滚到保存点 * * 执行流程 * 1. p1()开启主事务 * 2. registryUser()创建嵌套事务保存点1 * 3. insertLog()创建嵌套事务保存点2 * 4. insertLog()抛出异常回滚到保存点2 * 5. 异常向上抛出导致主事务也回滚 * 6. 结果所有数据都回滚 */ Transactional(propagation Propagation.NESTED) public void registryUser(String name, String password) { userInfoMapper.insert(name, password); log.info(用户数据插入成功); } } // LogService层修改 Slf4j Service public class LogService { Autowired private LogInfoMapper logInfoMapper; /** * 传播机制NESTED嵌套事务 * 行为创建嵌套事务 * * 执行流程 * 1. 在主事务中创建保存点 * 2. 在嵌套事务中执行操作 * 3. 抛出异常嵌套事务回滚 * 4. 异常继续抛出主事务也回滚 */ Transactional(propagation Propagation.NESTED) public void insertLog(String name, String op) { int a 10 / 0; // 嵌套事务内抛异常 logInfoMapper.insertLog(name, op); } }3.3.3.5NESTEDvsREQUIRED局部回滚关键区别代码演示// LogService层 - NESTED实现局部回滚 import org.springframework.transaction.interceptor.TransactionAspectSupport; // 手动回滚工具类 import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; Slf4j Service public class LogService { Autowired private LogInfoMapper logInfoMapper; /** * NESTED传播机制 手动回滚 局部回滚 * * 执行流程 * 1. p1()开启主事务 * 2. registryUser()在主事务中执行成功 * 3. insertLog()创建嵌套事务保存点 * 4. 发生异常捕获后在catch块中手动回滚嵌套事务 * 5. 主事务继续执行不受影响最终提交 * * 代码关键点 * - NESTED传播机制 * - catch块中调用setRollbackOnly() */ Transactional(propagation Propagation.NESTED) // 必须是NESTED public void insertLog(String name, String op) { try { // 业务逻辑 int a 10 / 0; // 模拟异常 // 正常插入日志 logInfoMapper.insertLog(name, op); log.info(日志记录成功); } catch (Exception e) { // 【核心】手动回滚当前嵌套事务 // 只回滚insertLog这个嵌套事务不影响主事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); log.error(日志记录失败但主事务继续执行, e); // 注意这里catch了异常主事务感知不到所以不会回滚 } } }对比REQUIRED的代码// 如果改为REQUIRED同样的代码会导致整个事务回滚 Transactional(propagation Propagation.REQUIRED) // 改为REQUIRED public void insertLog(String name, String op) { try { int a 10 / 0; logInfoMapper.insertLog(name, op); } catch (Exception e) { // 即使是REQUIRED在catch中setRollbackOnly也会标记整个事务为回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 主事务最终也会回滚 } }知识点与代码的对应关系传播机制代码体现异常处理方式结果REQUIREDTransactional(propagation Propagation.REQUIRED)catch中setRollbackOnly整个事务回滚NESTEDTransactional(propagation Propagation.NESTED)catch中setRollbackOnly仅嵌套事务回滚主事务继续四、完整项目代码超详细注释版4.1pom.xml依赖详细注释?xml version1.0 encodingUTF-8? !-- Maven项目对象模型文件 定义项目信息、依赖管理、构建配置等 -- project xmlns[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0) xmlns:xsi[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance) xsi:schemaLocation[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0) [https://maven.apache.org/xsd/maven-4.0.0.xsd](https://maven.apache.org/xsd/maven-4.0.0.xsd) !-- Maven模型版本固定为4.0.0 -- modelVersion4.0.0/modelVersion !-- 父项目配置spring-boot-starter-parent 作用提供Spring Boot的默认配置、依赖版本管理、插件配置等 好处无需手动指定每个依赖的版本号 -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.0/version !-- Spring Boot版本 -- relativePath/ !-- 不从本地路径查找父项目 -- /parent !-- 本项目信息 -- groupIdcom.example/groupId !-- 组织ID -- artifactIdspring-trans/artifactId !-- 项目ID -- version0.0.1-SNAPSHOT/version !-- 版本号 -- namespring-trans/name !-- 项目名称 -- descriptionSpring事务演示项目/description !-- 描述 -- !-- 属性配置 -- properties !-- Java版本 -- java.version11/java.version /properties !-- 依赖列表 -- dependencies !-- Spring Web Starter 包含Spring MVC、Tomcat、JSON处理等 作用提供Web应用基础功能 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis Spring Boot Starter 包含MyBatis、Spring JDBC、连接池等 作用简化MyBatis与Spring Boot的集成 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version !-- 指定版本 -- /dependency !-- MySQL驱动 scope: runtime表示编译时不依赖运行时由容器提供 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope !-- 只在运行时需要 -- /dependency !-- Lombok 作用通过注解减少样板代码getter、setter、toString等 optional: true表示不强制依赖 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Spring事务 虽然starter-web已包含但显式声明更清晰 -- dependency groupIdorg.springframework/groupId artifactIdspring-tx/artifactId /dependency /dependencies !-- 构建配置 -- build plugins !-- Spring Boot Maven插件 作用支持打包可执行jar、运行Spring Boot应用 -- plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes !-- 打包时排除Lombok -- exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project4.2application.properties配置详细注释# 数据库配置 # MySQL数据库连接URL # 参数说明 # - characterEncodingutf8字符编码为UTF-8 # - useSSLfalse不使用SSL连接 spring.datasource.urljdbc:mysql://127.0.0.1:3306/trans_test?characterEncodingutf8useSSLfalse # 数据库用户名 spring.datasource.usernameroot # 数据库密码 spring.datasource.passwordroot # MySQL驱动类名8.0版本 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # MyBatis配置 # 日志实现类在控制台输出SQL语句 mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl # 自动将下划线命名转换为驼峰命名 # 例如user_name字段 → userName属性 mybatis.configuration.map-underscore-to-camel-casetrue4.3 实体类详细注释// UserInfo.java import lombok.Data; // Lombok注解自动生成getter、setter、toString、equals、hashCode import java.util.Date; // 日期类 /** * 用户信息实体类 * 对应数据库表user_info * * Lombok的Data注解等价于 * - Getter生成所有字段的get方法 * - Setter生成所有字段的set方法 * - ToString生成toString方法 * - EqualsAndHashCode生成equals和hashCode方法 * - RequiredArgsConstructor生成必填字段的构造方法 */ Data // 使用Lombok减少样板代码 public class UserInfo { // 主键ID private Integer id; // 用户名对应表字段user_name private String userName; // 密码 private String password; // 创建时间 private Date createTime; // 更新时间 private Date updateTime; } // LogInfo.java import lombok.Data; import java.util.Date; /** * 日志信息实体类 * 对应数据库表log_info */ Data public class LogInfo { // 主键ID private Integer id; // 用户名 private String userName; // 操作类型 private String op; // 创建时间 private Date createTime; // 更新时间 private Date updateTime; }4.4 Mapper接口详细注释// UserInfoMapper.java import org.apache.ibatis.annotations.Insert; // MyBatis插入注解 import org.apache.ibatis.annotations.Mapper; // MyBatis Mapper标识注解 /** * 用户信息Mapper接口 * 作用定义操作user_info表的方法 * * Mapper注解告诉MyBatis这是Mapper接口生成代理实现类 */ Mapper // 必须加否则Spring无法扫描到 public interface UserInfoMapper { /** * 插入用户信息 * * Insert注解直接写SQL语句无需XML配置 * #{name}MyBatis的占位符会被方法参数name替换 * * param name 用户名 * param password 密码 * return 插入的行数成功返回1失败返回0 */ Insert(INSERT INTO user_info(user_name, password) VALUES(#{name}, #{password})) Integer insert(String name, String password); } // LogInfoMapper.java import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; /** * 日志信息Mapper接口 * 作用定义操作log_info表的方法 */ Mapper public interface LogInfoMapper { /** * 插入日志信息 * * param name 用户名 * param op 操作描述 * return 插入的行数 */ Insert(INSERT INTO log_info(user_name, op) VALUES(#{name}, #{op})) Integer insertLog(String name, String op); }4.5 Service层详细注释// UserService.java import com.example.demo.mapper.UserInfoMapper; // 用户Mapper import com.example.demo.model.UserInfo; // 用户实体 import lombok.extern.slf4j.Slf4j; // Lombok日志注解 import org.springframework.beans.factory.annotation.Autowired; // 依赖注入 import org.springframework.stereotype.Service; // 服务层注解 import org.springframework.transaction.annotation.Propagation; // 传播机制枚举 import org.springframework.transaction.annotation.Transactional; // 事务注解 /** * 用户服务类 * 处理用户注册等业务逻辑 * * Service注解标识这是一个服务层组件 * 作用1. 被Spring扫描注册为Bean 2. 支持AOP代理事务 */ Slf4j // 自动生成log日志对象 Service public class UserService { // 注入UserInfoMapper数据库操作 // AutowiredSpring自动装配将Spring容器中的Mapper注入 Autowired private UserInfoMapper userInfoMapper; /** * 注册用户核心业务方法 * * Transactional注解启动声明式事务 * propagation Propagation.REQUIRED如果调用者有事务就加入否则新建 * * 重要Service层是事务的最佳放置位置 * 原因Service层代表业务逻辑一个业务可能包含多个DAO操作 */ Transactional(propagation Propagation.REQUIRED) public void registryUser(String name, String password) { // 调用Mapper插入数据 // 如果插入失败抛异常事务会自动回滚 userInfoMapper.insert(name, password); // 记录成功日志 // {}是占位符会被后面的参数替换比字符串拼接性能更好 log.info(用户数据插入成功用户名{}, name); } } // LogService.java import com.example.demo.mapper.LogInfoMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; // 手动回滚工具 /** * 日志服务类 * 负责记录用户操作日志 * * 设计思路日志记录不应该影响主业务 * 即使日志记录失败用户注册也应该成功 * 因此使用NESTED实现局部回滚 */ Slf4j Service public class LogService { // 注入LogInfoMapper Autowired private LogInfoMapper logInfoMapper; /** * 插入日志方法 * * Transactional注解 * propagation Propagation.NESTED创建嵌套事务 * * NESTED的特殊能力 * 1. 可以捕获异常并手动回滚嵌套事务 * 2. 不影响父事务的提交 * * 对比REQUIRED * 如果是REQUIRED即使catch了异常只要setRollbackOnly() * 整个事务包括父事务都会回滚 */ Transactional(propagation Propagation.NESTED) public void insertLog(String name, String op) { try { // 模拟业务逻辑 log.info(开始记录日志用户{}, 操作{}, name, op); // 模拟异常比如日志服务器不可用 int a 10 / 0; // 抛出ArithmeticException // 正常插入日志 logInfoMapper.insertLog(name, op); log.info(日志记录成功); } catch (Exception e) { // 【核心】手动回滚嵌套事务 // TransactionAspectSupport.currentTransactionStatus()获取当前事务状态 // setRollbackOnly()标记当前事务为仅回滚 // 对于NESTED只回滚嵌套事务对于REQUIRED标记整个事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 记录错误日志但不抛出异常 // 这样父事务就不会感知到异常会继续提交 log.error(日志记录失败但主业务不受影响, e); } } }4.6 Controller层详细注释// TransactionalController.java import com.example.demo.service.LogService; // 日志服务 import com.example.demo.service.UserService; // 用户服务 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; // 传播机制 import org.springframework.transaction.annotation.Transactional; // 事务注解 import org.springframework.web.bind.annotation.RequestMapping; // 请求映射 import org.springframework.web.bind.annotation.RestController; // REST控制器 /** * 事务测试控制器 * * RestController Controller ResponseBody * 作用1. 声明控制器 2. 所有方法返回JSON数据 * * RequestMapping(/trans) * 类级别的路径映射所有方法路径前都有/trans前缀 */ RestController RequestMapping(/trans) public class TransactionalController { // 注入用户服务 Autowired private UserService userService; // 注入日志服务 Autowired private LogService logService; /** * 注册用户并记录日志 * * 完整业务流程 * 1. 开启主事务REQUIRED * 2. 注册用户NESTED嵌套事务 * 3. 记录日志NESTED嵌套事务可局部回滚 * 4. 主事务提交 * * param name 用户名 * param password 密码 * return 操作结果 */ RequestMapping(/registry) // 完整路径/trans/registry Transactional(propagation Propagation.REQUIRED) // 开启主事务 public String registry(String name, String password) { // 第一步注册用户 // 如果UserService是NESTED这里创建嵌套事务 // 如果UserService是REQUIRED这里加入当前事务 userService.registryUser(name, password); // 第二步记录日志 // 即使日志记录失败被catch也不会影响主事务 // 因为LogService中catch了异常并setRollbackOnly() logService.insertLog(name, 用户注册); // 方法正常返回主事务自动提交 // 如果抛出RuntimeException主事务自动回滚 return 注册成功; } }完整调用流程注释// 浏览器请求GET /trans/registry?nameTompassword123 // Spring MVC处理流程 // 1. DispatcherServlet接收请求 // 2. 找到TransactionalController.registry()方法 // 3. 发现Transactional注解创建代理对象 // 4. 代理对象执行 // a. 开启主事务REQUIRED // b. 调用registry()方法 // i. userService.registryUser() → 嵌套事务插入用户成功 // ii. logService.insertLog() → 嵌套事务抛出异常但被catch // - 在catch中setRollbackOnly() → 只回滚嵌套事务 // - 异常被捕获不向上抛出 // c. registry()方法正常结束 // d. 提交主事务用户数据保留 // 5. 返回注册成功 // 数据库结果 // user_info表Tom用户插入成功 // log_info表没有日志因为嵌套事务回滚了4.7 数据库初始化脚本详细注释-- trans_test.sql -- 删除已存在的数据库如果存在 -- 用于重新初始化测试环境 DROP DATABASE IF EXISTS trans_test; -- 创建新数据库 -- DEFAULT CHARACTER SET utf8mb4设置默认字符集为UTF-8支持emoji CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4; -- 用户表 -- 删除已存在的表 DROP TABLE IF EXISTS user_info; -- 创建用户表 CREATE TABLE user_info( -- 主键ID自增 id INT NOT NULL AUTO_INCREMENT, -- 用户名VARCHAR(128)表示最大128字符NOT NULL表示不能为空 user_name VARCHAR(128) NOT NULL, -- 密码VARCHAR(128) password VARCHAR(128) NOT NULL, -- 创建时间DATETIME类型DEFAULT now()表示默认当前时间 create_time DATETIME DEFAULT now(), -- 更新时间ON UPDATE now()表示每次更新记录时自动更新为当前时间 update_time DATETIME DEFAULT now() ON UPDATE now(), -- 设置主键 PRIMARY KEY(id) -- 存储引擎和字符集 ) ENGINEINNODB -- INNODB支持事务MyISAM不支持 DEFAULT CHARACTER SETutf8mb4 COMMENT用户表; -- 表注释 -- 日志表 DROP TABLE IF EXISTS log_info; CREATE TABLE log_info( id INT PRIMARY KEY auto_increment, -- 主键简写形式 user_name VARCHAR(128) NOT NULL, op VARCHAR(256) NOT NULL COMMENT 操作描述, -- 字段注释 create_time DATETIME DEFAULT now(), update_time DATETIME DEFAULT now() ON UPDATE now() ) DEFAULT charsetutf8mb4;五、总结与代码对应关系5.1 Spring事务实现方式对比// 方式一编程式事务手动控制 public void programmatic() { // 代码体现手动getTransaction、commit、rollback TransactionStatus status manager.getTransaction(definition); try { // 业务代码 manager.commit(status); } catch(Exception e) { manager.rollback(status); throw e; } } // 方式二声明式事务注解控制 Transactional // 代码体现一个注解搞定所有事务管理 public void declarative() { // 业务代码 // Spring自动处理开启、提交、回滚 }5.2Transactional关键属性代码体现Transactional( // 1. rollbackFor异常回滚 rollbackFor Exception.class, // 代码指定IOException等检查型异常也回滚 // 2. isolation隔离级别 isolation Isolation.READ_COMMITTED, // 代码避免脏读 // 3. propagation传播机制 propagation Propagation.REQUIRED // 代码决定事务如何在方法间传播 ) public void method() { // 业务代码 }5.3 事务传播机制总结表// 代码体现在Service方法上添加不同注解 // REQUIRED默认 Transactional(propagation Propagation.REQUIRED) public void requiredMethod() { // 加入当前事务一损俱损 } // REQUIRES_NEW独立事务 Transactional(propagation Propagation.REQUIRES_NEW) public void requiresNewMethod() { // 新建事务挂起调用者事务 } // NESTED嵌套事务 Transactional(propagation Propagation.NESTED) public void nestedMethod() { // 创建嵌套事务可实现局部回滚 try { // 业务代码 } catch(Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 只回滚嵌套事务 } } // NEVER不支持事务 Transactional(propagation Propagation.NEVER) public void neverMethod() { // 调用者有事务就抛异常 }六、常见问题与代码解答Q1为什么要在Service层而不是Controller层使用事务代码说明// ❌ 错误在Controller层放事务 RestController public class UserController { Transactional public String register() { // Controller应该只负责请求和响应 // 如果业务逻辑复杂调用多个Service事务范围会过大 userService.insertUser(); // 操作1 orderService.createOrder(); // 操作2 logService.recordLog(); // 操作3 // 事务范围包含所有操作性能差 } } // ✅ 正确在Service层放事务 Service public class UserService { Transactional public void registerUser() { // Service层代表业务逻辑 // 事务范围只包含当前业务 userMapper.insert(); logMapper.insert(); // 相关业务在同一个事务 } }结论代码联系Transactional放在Service层事务边界更清晰符合单一职责原则。Q2NESTED和REQUIRED有什么区别代码对比// REQUIRED全部回滚 Transactional(propagation Propagation.REQUIRED) public void parent() { child(); // REQUIRED加入当前事务 } Transactional(propagation Propagation.REQUIRED) public void child() { // 抛异常 → 整个事务parentchild回滚 throw new RuntimeException(); } // NESTED局部回滚 Transactional(propagation Propagation.REQUIRED) public void parent() { child(); // NESTED创建嵌套事务 } Transactional(propagation Propagation.NESTED) public void child() { try { throw new RuntimeException(); } catch(Exception e) { // 只回滚嵌套事务parent继续 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }结论代码联系Propagation.NESTEDsetRollbackOnly()实现了局部回滚而REQUIRED会回滚整个事务。Q3如何在事务中捕获异常并实现部分回滚答案代码/** * 答案使用NESTED传播机制 手动setRollbackOnly() */ Transactional(propagation Propagation.NESTED) // 关键1必须是NESTED public void partialRollback() { try { // 可能失败的操作 riskOperation(); } catch(Exception e) { // 关键2手动回滚嵌套事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 记录日志或进行补偿操作 log.error(操作失败但主事务继续, e); } // 主业务逻辑继续执行 mainBusiness(); // 结果riskOperation()回滚mainBusiness()提交 }七、扩展知识点7.1 事务ACID特性的代码体现Transactional public void demonstrateACID() { // 原子性Atomicity // 代码体现try块中的所有操作要么全部commit要么全部rollback try { insertUser(); // 操作1 insertOrder(); // 操作2 updateStock(); // 操作3 // 全部成功 → commit() } catch(Exception e) { // 任何失败 → rollback() } // 一致性Consistency // 代码体现事务开始前和结束后数据库约束不被破坏 // 隔离性Isolation // 代码体现Transactional(isolation Isolation.READ_COMMITTED) // 持久性Durability // 代码体现commit()后数据永久保存即使系统崩溃 }7.2 事务实现原理AOP代理// Spring AOP代理伪代码帮助你理解Transactional如何工作 public class UserServiceProxy extends UserService { private UserService target; // 真实对象 private TransactionManager txManager; // 事务管理器 Override public void registryUser(String name, String password) { // 1. 解析Transactional注解 TransactionDefinition definition parseTransactionAnnotation(); // 2. 开启事务AOP前置通知 TransactionStatus status txManager.getTransaction(definition); try { // 3. 调用真实对象的方法 target.registryUser(name, password); // 4. 提交事务AOP返回通知 txManager.commit(status); } catch(RuntimeException e) { // 5. 回滚事务AOP异常通知 txManager.rollback(status); throw e; } } }八、最佳实践代码模板/** * Service层事务最佳实践模板 */ Service public class BestPracticeService { /** * 1. 默认使用REQUIRED * 适用场景大多数业务操作 */ Transactional // 不指定propagation默认就是REQUIRED public void standardBusiness() { // 多个数据库操作 } /** * 2. 需要独立事务使用REQUIRES_NEW * 适用场景发送邮件、记录审计日志等不影响主业务的操作 */ Transactional(propagation Propagation.REQUIRES_NEW) public void independentOperation() { // 即使失败也不影响主事务 } /** * 3. 需要局部回滚使用NESTED * 适用场景主订单必须成功但订单明细可以失败 */ Transactional(propagation Propagation.NESTED) public void nestedOperation() { try { // 可能失败的操作 } catch(Exception e) { // 局部回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } /** * 4. 只读查询使用readOnly true * 作用优化性能数据库可能启用只读优化 */ Transactional(readOnly true) public UserInfo getUserInfo(Integer userId) { // 只读查询 return userMapper.selectById(userId); } /** * 5. 指定超时时间 * 作用防止长时间占用数据库连接 */ Transactional(timeout 30) // 30秒超时 public void longTimeOperation() { // 可能耗时的操作 } }最后的话记住这个核心原则// 看到代码时问自己三个问题 Transactional(propagation Propagation.REQUIRED, isolation Isolation.READ_COMMITTED, rollbackFor Exception.class) public void method() { // 1. 当前有事务吗传播机制决定 // 2. 能看到其他事务未提交的数据吗隔离级别决定 // 3. 抛 checked exception会回滚吗rollbackFor决定 }希望这份带详细注释的指南能帮你彻底理解Spring事务每个代码片段都与知识点直接挂钩注释覆盖了从import到方法返回的每一个细节。八、Spring事务失效场景详解新手必看本章目标掌握Spring事务的8种常见失效场景避免在实际开发中踩坑。这是从会用到用好的关键一步8.1 同类自调用问题最坑问题描述在同一个类中非事务方法调用本类的事务方法事务会完全失效。这是新手最容易踩、最难排查的坑。错误代码示例import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 用户服务类 - 演示同类自调用导致事务失效 */ Slf4j Service public class UserService { /** * 外部调用的入口方法无事务 * 问题这个方法本身没有Transactional它直接调用内部方法 */ public void registerUser(String name, String password) { log.info(开始注册用户); // 【致命错误】直接调用本类的方法使用this调用 // this指向的是目标对象本身而不是Spring的代理对象 // 结果就是绕过了Spring AOP代理Transactional注解被完全忽略 this.insertUser(name, password); // 事务失效 log.info(注册用户结束); } /** * 内部事务方法带Transactional * 问题虽然加了事务注解但同类调用时不会生效 */ Transactional public void insertUser(String name, String password) { log.info(插入用户数据到数据库); userInfoMapper.insert(name, password); // 模拟异常 int i 10 / 0; // 抛出RuntimeException // 期望事务回滚数据库没有数据 // 实际事务失效数据库有数据操作未回滚 } }失效原因分析// Spring AOP代理工作原理伪代码 public class UserService$$Proxy extends UserService { private UserService target; // 真实的目标对象 private TransactionInterceptor interceptor; // 事务拦截器 Override public void insertUser(String name, String password) { // 代理对象会在这里开启事务 TransactionStatus status txManager.getTransaction(definition); try { target.insertUser(name, password); // 调用真实对象的方法 txManager.commit(status); } catch(Exception e) { txManager.rollback(status); throw e; } } } // 当同类自调用时 public void registerUser() { // this指向的是UserService目标对象不是UserService$$Proxy代理对象 // 绕过了AOP代理直接调用目标方法事务拦截器完全不生效 this.insertUser(); // 没有AOP代理介入 }正确解决方案import org.springframework.beans.factory.annotation.Autowired; // 注入自身代理对象 import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 正确做法注入自己的代理对象通过代理对象调用 */ Slf4j Service public class UserService { // 【解决方案1】注入Spring容器中的代理对象 // Spring会注入一个代理后的UserService实例 Autowired private UserService selfProxy; // 自己代理自己 public void registerUser(String name, String password) { log.info(开始注册用户); // 通过代理对象调用AOP生效 // Spring会拦截这次调用执行事务逻辑 selfProxy.insertUser(name, password); // ✅ 事务有效 log.info(注册用户结束); } Transactional public void insertUser(String name, String password) { userInfoMapper.insert(name, password); int i 10 / 0; // 异常后事务正确回滚 } // 【解决方案2】将方法拆分到不同Service // UserService调用LogService天然跨类调用AOP永远生效 Autowired private LogService logService; // 注入其他Service public void registerUser2(String name, String password) { this.insertUser(name, password); // 本类方法无事务 logService.recordLog(name); // 其他类方法有事务AOP生效 } }8.2 方法修饰符非public问题描述Spring AOP基于代理实现private、protected、package-private方法上的Transactional不会生效。错误代码示例import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 演示非public方法事务失效 */ Slf4j Service public class OrderService { /** * 公共方法调用私有事务方法 */ public void createOrder(Order order) { // 调用私有方法 this.saveOrder(order); // 事务失效 } /** * 【错误】私有方法上的事务注解 * 原因Spring AOP代理无法拦截私有方法 * JDK动态代理基于接口只能代理public方法 * CGLIB代理虽然可以代理类但默认不拦截非public方法 */ Transactional private void saveOrder(Order order) { orderMapper.insert(order); // 数据库操作 int i 10 / 0; // 期望回滚但不会回滚 } /** * 【错误】protected方法上的事务注解 */ Transactional protected void updateOrder(Order order) { orderMapper.update(order); } /** * 【错误】package-private方法上的事务注解 */ Transactional void deleteOrder(Integer orderId) { orderMapper.delete(orderId); } }正确解决方案Slf4j Service public class OrderService { /** * 【正确】将事务注解放在public方法上 * Spring AOP代理可以正常拦截public方法 */ Transactional public void createOrder(Order order) { // 所有数据库操作都在同一个事务中 orderMapper.insert(order); orderDetailMapper.insert(order.getItems()); int i 10 / 0; // 异常后正确回滚 } /** * 【正确】提取到独立的Service类 * 跨类调用即使是package-private也生效因为走代理 */ Service public class OrderInnerService { Transactional public void saveOrder(Order order) { orderMapper.insert(order); } } }8.3 方法是final/static问题描述final方法不能被重写CGLIB无法生成代理子类static方法属于类不属于实例无法被代理。错误代码示例import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 演示final/static方法事务失效 */ Slf4j Service public class ProductService { /** * 【错误】final方法上的事务注解 * 原因CGLIB代理通过继承类生成代理子类无法重写final方法 */ Transactional public final void addProduct(Product product) { productMapper.insert(product); // 事务失效 int i 10 / 0; } /** * 【错误】static方法上的事务注解 * 原因static方法属于类代理对象无法拦截 */ Transactional public static void updateProduct(Product product) { // 静态方法无法在Spring容器中管理 // 也就无法创建代理 } }正确解决方案Slf4j Service public class ProductService { /** * 【正确】去掉final修饰符 * CGLIB可以正常生成代理子类并重写此方法 */ Transactional public void addProduct(Product product) { productMapper.insert(product); } /** * 【正确】static方法不要加事务 * 如果需要事务改为实例方法 */ Transactional public void updateProduct(Product product) { productMapper.update(product); } }8.4 多线程调用问题问题描述Spring事务基于ThreadLocal实现绑定在当前线程。新线程无法获取主线程的事务。错误代码示例import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 演示多线程导致事务失效 */ Slf4j Service public class MultiThreadService { // 线程池 private ExecutorService executor Executors.newFixedThreadPool(5); Transactional public void createOrderWithAsyncLog(Order order) { // 主线程中插入订单在主事务中 orderMapper.insert(order); log.info(订单插入成功主线程事务ID{}, TransactionSynchronizationManager.getCurrentTransactionName()); // 【致命错误】在新线程中记录日志 // 新线程无法获取主线程的事务会创建新连接 executor.submit(() - { log.info(新线程开始记录日志事务ID{}, TransactionSynchronizationManager.getCurrentTransactionName()); // 问题1这个操作不在主事务中无法回滚 logMapper.insert(order.getLog()); // 问题2如果这里抛出异常主线程的事务不会感知 // 问题3可能导致数据不一致订单提交了日志没记录 int i 10 / 0; // 新线程异常不影响主事务 }); // 主线程继续执行 int j 10 / 0; // 主线程异常订单回滚但日志可能已在新线程中插入 } }正确解决方案import org.springframework.scheduling.annotation.Async; // 异步注解 import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; // 事务同步 import org.springframework.transaction.support.TransactionSynchronizationManager; // 事务同步管理器 /** * 正确做法使用事务同步管理器或异步事务 */ Slf4j Service public class MultiThreadService { /** * 方案1使用TransactionSynchronization补偿机制 * 在事务提交/回滚后执行操作 */ Transactional public void createOrderWithSync(Order order) { orderMapper.insert(order); // 注册事务同步回调 TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { Override public void afterCommit() { // 事务提交成功后在新线程中执行 executor.submit(() - { logMapper.insert(order.getLog()); }); } Override public void afterCompletion(int status) { if (status STATUS_ROLLED_BACK) { log.error(事务回滚不记录日志); } } } ); } /** * 方案2重构业务逻辑避免在事务中开新线程 * 先完成事务再异步处理 */ public void createOrder(Order order) { // 1. 先同步完成事务操作 transactionTemplate.execute(status - { orderMapper.insert(order); return true; }); // 2. 事务完成后再异步执行其他操作 executor.submit(() - { logMapper.insert(order.getLog()); }); } /** * 方案3使用Async 独立事务 * 注意这里的Async方法必须放在不同的类中 */ Service public class LogService { Async // Spring异步执行 Transactional(propagation Propagation.REQUIRES_NEW) public void asyncInsertLog(Log log) { // 独立事务独立线程 logMapper.insert(log); } } }8.5 异常被catch未抛出问题描述事务方法中捕获了异常但没有重新抛出Spring感知不到异常不会回滚。错误代码示例import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 演示异常被吞导致事务失效 */ Slf4j Service public class ExceptionHandleService { Transactional public void createOrder(Order order) { try { orderMapper.insert(order); // 模拟业务异常 int i 10 / 0; // 抛出ArithmeticException } catch (Exception e) { log.error(创建订单失败{}, e.getMessage()); // 【致命错误】没有重新抛出异常 // Spring认为方法正常执行完毕会提交事务 // 订单数据会残留在数据库中不完整状态 } // 这里还会继续执行导致逻辑错误 log.info(订单创建成功); // 实际上失败了 } }正确解决方案Slf4j Service public class ExceptionHandleService { Transactional public void createOrder(Order order) { try { orderMapper.insert(order); int i 10 / 0; } catch (Exception e) { log.error(创建订单失败{}, e.getMessage()); // 【正确】重新抛出异常让Spring感知到 // Spring捕获异常后会回滚事务 throw new RuntimeException(订单创建失败, e); // ✅ 事务回滚 // 或者抛出自定义业务异常 // throw new BusinessException(ORDER_CREATE_FAILED); } } /** * 方案2如果需要特殊处理手动标记回滚 * 场景异常不能抛出但又要回滚事务 */ Transactional public void createOrder2(Order order) { try { orderMapper.insert(order); int i 10 / 0; } catch (Exception e) { log.error(创建订单失败{}, e.getMessage()); // 手动标记事务为只回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 不抛出异常但事务仍会回滚 return; // 方法正常返回但事务已标记为回滚 } } }8.6 数据库引擎不支持事务问题描述MySQL的MyISAM引擎不支持事务即使代码正确事务也不会生效。问题代码SQL层面-- 创建使用MyISAM引擎的表 CREATE TABLE user_info_myisam ( id INT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(128) ) ENGINEMYISAM; -- MyISAM不支持事务 -- 插入数据 START TRANSACTION; INSERT INTO user_info_myisam(user_name) VALUES(Tom); ROLLBACK; -- 回滚无效数据已经插入正确解决方案-- 使用InnoDB引擎支持事务 CREATE TABLE user_info_innodb ( id INT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(128) ) ENGINEINNODB; -- InnoDB支持事务 -- 现在事务可以正常回滚 START TRANSACTION; INSERT INTO user_info_innodb(user_name) VALUES(Tom); ROLLBACK; -- 数据正确回滚表中无数据Spring中的检查代码import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; // 初始化回调注解 /** * 检查数据库引擎是否支持事务 */ Component public class DatabaseEngineChecker { Autowired private JdbcTemplate jdbcTemplate; /** * PostConstructSpring初始化Bean后执行 * 作用在应用启动时检查配置 */ PostConstruct public void checkEngine() { String sql SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA trans_test AND TABLE_NAME user_info; String engine jdbcTemplate.queryForObject(sql, String.class); if (!InnoDB.equalsIgnoreCase(engine)) { log.error(警告user_info表引擎是{}不支持事务, engine); // 可以抛异常阻止应用启动 throw new IllegalStateException(数据库引擎不支持事务); } log.info(检查通过数据库引擎支持事务); } }8.7 未配置事务管理器问题描述没有配置DataSourceTransactionManagerSpring无法管理事务。错误配置Spring Boot// application.properties中未配置数据源 // spring.datasource.url... (缺失) // spring.datasource.username... (缺失) // 启动类缺少EnableTransactionManagement SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } // 结果Transactional注解被忽略事务失效正确配置/** * 显式配置事务管理器Spring Boot通常自动配置但了解原理很重要 */ import org.springframework.context.annotation.Bean; // Bean定义 import org.springframework.context.annotation.Configuration; // 配置类 import org.springframework.jdbc.datasource.DataSourceTransactionManager; // 事务管理器 import org.springframework.transaction.PlatformTransactionManager; // 事务管理器接口 import org.springframework.transaction.annotation.EnableTransactionManagement; // 开启事务管理 import javax.sql.DataSource; // 数据源 /** * 事务配置类 * * EnableTransactionManagement启用Spring的注解事务管理 * 作用1. 创建事务代理 2. 启用Transactional注解解析 * * 在Spring Boot中只要引入了spring-boot-starter-jdbc通常自动配置 * 但显式配置有助于理解原理 */ Configuration EnableTransactionManagement // ✅ 开启事务管理 public class TransactionConfig { /** * 定义事务管理器Bean * * param dataSource 数据源Spring自动注入 * return 事务管理器 */ Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { // DataSourceTransactionManager是JDBC的事务管理器 // 它管理数据库连接的获取、提交、回滚 return new DataSourceTransactionManager(dataSource); } }8.8 传播机制设置错误问题描述错误使用NEVER/NOT_SUPPORTED等传播机制导致事务意外失效或抛出异常。错误代码示例import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * 演示传播机制误用 */ Slf4j Service public class PropagationMisuseService { Transactional public void mainBusiness() { // 主业务逻辑 // 【致命错误】调用NEVER传播机制的方法 // NEVER要求当前没有事务但mainBusiness()有事务 // 结果抛出IllegalTransactionStateException异常 this.noTransactionMethod(); } Transactional(propagation Propagation.NEVER) // 要求无事务环境 public void noTransactionMethod() { log.info(此方法不应在事务中执行); } // 另一个误用场景 Transactional public void anotherBusiness() { // 【致命错误】调用NOT_SUPPORTED挂起当前事务 // 导致主事务被挂起操作不在事务保护中 this.notSupportedMethod(); // 后续操作在主事务中但前面的操作已脱离事务 } Transactional(propagation Propagation.NOT_SUPPORTED) public void notSupportedMethod() { // 此方法以非事务方式运行当前事务被挂起 // 如果这里操作数据库失败不会回滚 logMapper.insert(日志); // 不受主事务保护 } }正确解决方案Slf4j Service public class PropagationMisuseService { Transactional public void mainBusiness() { // 主业务逻辑 // 【正确】如果需要独立事务使用REQUIRES_NEW this.independentMethod(); // 创建新事务不影响主事务 } Transactional(propagation Propagation.REQUIRES_NEW) public void independentMethod() { log.info(此方法在独立事务中执行); logMapper.insert(日志); // 即使这里失败也只回滚自己的事务 } /** * 【正确】明确传播机制的使用场景表 */ public class PropagationGuide { // 场景1大多数业务 → REQUIRED默认 Transactional(propagation Propagation.REQUIRED) public void normalBusiness() { // 加入当前事务 } // 场景2必须独立 → REQUIRES_NEW Transactional(propagation Propagation.REQUIRES_NEW) public void mustIndependent() { // 审计日志、消息通知等 } // 场景3部分回滚 → NESTED Transactional(propagation Propagation.NESTED) public void partialRollback() { // 可选操作失败不影响主流程 } // 场景4查询 → readOnly true Transactional(readOnly true) public void query() { // 只读查询优化性能 } } }8.9 总结事务失效检查清单/** * 在怀疑事务失效时按此清单检查代码 */ public class TransactionChecklist { public void checkBeforeCommit() { // □ 1. 方法是否是public // if (!Modifier.isPublic(method.getModifiers())) return false; // □ 2. 方法是否是final/static // if (Modifier.isFinal(method.getModifiers())) return false; // □ 3. 是否是同类自调用 // if (invocation.getThis() target) return false; // □ 4. 异常是否正确抛出 // if (exceptionCaughtButNotRethrown) return false; // □ 5. 传播机制是否正确 // if (propagation Propagation.NEVER hasTransaction) return false; // □ 6. 数据库引擎是否支持 // if (engine ! InnoDB) return false; // □ 7. 事务管理器是否配置 // if (txManager null) return false; // □ 8. 是否在多线程中调用 // if (Thread.currentThread() ! mainThread) return false; } }终极建议写完后问自己一句我的代码真的能走到Spring的代理逻辑里吗 只要记住这一点80%的事务失效问题都能避免。