Spring 事务管理详解在 Spring 项目开发中数据库操作常常需要保证一组操作要么全部成功、要么全部失败——这就是事务存在的意义。Spring 对数据库事务提供了非常友好的封装让我们可以通过简单的注解就完成事务管理而不需要手动写大量的commit/rollback代码。本文按基础概念 → 注解使用 → 隔离级别 → 传播机制的顺序展开读完你应该能独立在 Spring 项目中正确配置和使用事务管理。事务的基础概念事务的操作主要有三步开启事务START TRANSACTION/BEGIN—— 在一组操作前开启事务提交事务COMMIT—— 这组操作全部成功时提交事务让数据持久化回滚事务ROLLBACK—— 这组操作中任何一个出现异常回滚事务撤销所有修改用伪 SQL 来表示就是这样BEGIN;UPDATEaccountSETbalancebalance-100WHEREid1;-- A 转出UPDATEaccountSETbalancebalance100WHEREid2;-- B 到账COMMIT;-- 两条都成功 → 提交-- 如果任一条失败 → ROLLBACK 回滚事务的核心特性就是 ACID原子性、一致性、隔离性、持久性这里不再展开。Spring 事务的两种实现方式Spring 对事务的实现分为两类方式说明使用场景编程式事务手动通过TransactionTemplate或PlatformTransactionManager控制事务边界需要精细控制事务的场景代码侵入性强声明式事务通过Transactional注解声明事务底层基于 AOP 实现绝大多数业务场景代码简洁推荐使用在项目中绝大多数情况使用声明式事务就够了。声明式事务的原理是 Spring AOPSpring 会为标注了Transactional的 Bean 生成代理对象在方法执行前后自动开启和提交/回滚事务。调用方 → 代理对象AOP→ 开启事务 → 执行目标方法 → 提交/回滚事务如果对 AOP 还不太熟悉可以参考 [[Spring AOP]]。Transactional 注解详解基本用法Transactional注解表示开启了一个事务方法正常执行完成→ 事务自动提交方法抛出异常Error或RuntimeException及其子类→ 事务自动回滚这里有一个重要的细节异常被try-catch捕获后事务会正常提交因为 Spring 感知不到异常。如果需要回滚有两种做法TransactionalpublicvoiddoSomething(){try{// 业务逻辑}catch(Exceptione){// 方案1重新抛出异常让 Spring 感知并回滚thrownewRuntimeException(e);// 方案2手动标记回滚不抛出异常时使用// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}}代码位置Service 层方法上关键点try-catch吞掉异常会导致事务不回滚需要时可用setRollbackOnly()手动回滚作用范围Transactional可以用来修饰方法或类修饰方法时只有修饰public方法时才生效修饰private/protected方法不会报错但也不生效——推荐修饰类时对该类中所有的public方法都生效为什么只对public方法生效因为 Spring 事务基于 AOP 动态代理实现代理对象只能拦截public方法。rollbackFor 属性默认情况下只有RuntimeException和Error会触发回滚。如果希望**受检异常checked exception**也触发回滚需要通过rollbackFor指定// 让 IOException 也触发回滚Transactional(rollbackFor{IOException.class})// 可以指定多个异常类型数组形式Transactional(rollbackFor{IOException.class,SQLException.class})代码位置Transactional注解的参数解决的问题默认不回滚受检异常如IOException业务需要时通过rollbackFor扩展注意事项rollbackFor接受一个Class数组花括号{}是数组语法事务的隔离级别为什么需要隔离级别同一个项目中不同业务对数据一致性的要求不同。比如财务报表要求读到绝对准确的数据不能出现脏读商品列表页面可以容忍少量不一致追求更高并发性能Spring 允许我们手动设置事务隔离级别在一致性和性能之间做取舍。SQL 标准隔离级别回顾隔离级别脏读不可重复读幻读READ UNCOMMITTED读未提交✓✓✓READ COMMITTED读已提交✗✓✓REPEATABLE READ可重复读✗✗✓SERIALIZABLE串行化✗✗✗MySQL 默认隔离级别是REPEATABLE READ可重复读。Spring 中的 5 种隔离级别Transactional(isolationIsolation.READ_COMMITTED)Spring 常量对应 SQL 标准说明Isolation.DEFAULT—以数据库默认隔离级别为准最常用Isolation.READ_UNCOMMITTEDREAD UNCOMMITTED读未提交Isolation.READ_COMMITTEDREAD COMMITTED读已提交解决脏读Isolation.REPEATABLE_READREPEATABLE READ可重复读MySQL 默认级别Isolation.SERIALIZABLESERIALIZABLE串行化最高隔离级别代码位置Transactional注解的isolation属性关键点大多数情况用DEFAULT即可让数据库自己决定注意事项隔离级别越高数据一致性越好但并发性能越差事务的传播机制什么是事务传播当一个 Service 方法调用另一个 Service 方法且两者都有事务时就产生了事务传播——被调用方法的事务如何处理与调用方事务的关系ServicepublicclassAService{AutowiredprivateBServicebService;Transactionalpublicvoidorder(){// A 的事务中调用了 B 的方法bService.doSomething();// B 也有事务 → 产生传播// 其他业务逻辑}}三种基本形态先理解三种最基本的事务关系形态说明回滚影响融入A 和 B 是同一个事务A 回滚 → B 回滚B 回滚 → A 回滚挂起A 和 B 是两个独立的事务A 回滚 → B 不回滚互不影响嵌套B 的事务嵌套在 A 的事务里B 回滚 → A 不回滚局部回滚A 回滚 → B 回滚用伪 SQL 来直观理解融入同一个事务BEGIN;UPDATEyyyy;UPDATExxxx;-- B 的操作共用 A 的事务UPDATEzzzz;COMMIT;挂起两个独立事务BEGIN;-- A 的事务UPDATEyyyy;-- A 的事务挂起保存状态 --BEGIN;-- B 的事务独立UPDATExxxx;COMMIT;-- B 提交-- A 的事务恢复 --UPDATEzzzz;COMMIT;-- A 提交嵌套B 嵌套在 A 里BEGIN;-- A 的事务UPDATEyyyy;SAVEPOINTsp;-- 保存点UPDATExxxx;-- B 的操作-- B 失败 → ROLLBACK TO sp局部回滚A 不受影响--UPDATEzzzz;COMMIT;Spring 的 7 种传播行为传播行为A 有事务A 无事务回滚特点REQUIRED默认B 融入 AB 新建事务任一失败全部回滚SUPPORTSB 融入 AB 以非事务方式运行有事务时随 A 回滚无事务时不回滚MANDATORYB 融入 A抛出异常强制要求调用方有事务REQUIRES_NEWA 挂起B 新建独立事务B 新建事务A 和 B 互不影响NOT_SUPPORTEDA 挂起B 以非事务运行B 以非事务运行B 不回滚NEVER抛出异常B 以非事务运行不允许在有事务的环境运行NESTEDB 嵌套在 A 的事务中B 新建事务B 回滚不影响 A局部回滚重点NESTED vs REQUIRED这两个容易混淆关键区别在于局部回滚REQUIREDB 和 A 是同一个事务任何一方失败 →全部回滚NESTEDB 嵌套执行B 失败 → 只有 B 的部分回滚A 不受影响嵌套事务之所以能实现局部回滚是因为数据库的**保存点Savepoint**机制。Spring 利用保存点在嵌套事务失败时只回滚到保存点位置。设置传播行为Transactional(propagationPropagation.REQUIRED)publicvoiddoSomething(){...}Transactional(propagationPropagation.REQUIRES_NEW)publicvoiddoIndependent(){...}Transactional(propagationPropagation.NESTED)publicvoiddoNested(){...}手动回滚当前事务无论哪种传播机制都可以在代码中手动标记回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();代码位置事务方法内部解决的问题在不抛出异常的情况下回滚事务如业务校验不通过注意事项这只回滚当前事务如果是NESTED只回滚嵌套事务部分常见问题与注意事项Transactional不生效排查清单方法是不是public—— 非 public 方法不生效是不是同类内部调用—— 同类中方法 A 直接调用方法 BB 的Transactional不走代理不生效异常是不是被try-catch吃掉了—— Spring 感知不到异常就不会回滚数据库引擎是否支持事务—— 比如 MySQL 的 MyISAM 引擎不支持事务什么时候用 REQUIRES_NEW典型场景日志记录。即使业务操作失败回滚日志记录也应该独立提交不受主事务回滚的影响。大多数情况怎么选隔离级别用DEFAULT交给数据库传播行为用REQUIRED默认能满足 90% 的场景没有明确需求时不要去改默认值总结Spring 事务管理的核心就四个点Transactional注解声明式事务基于 AOP 实现作用在public方法上回滚规则默认RuntimeException和Error回滚try-catch捕获后不回滚除非手动标记隔离级别5 种大多数情况用DEFAULT传播机制7 种默认REQUIRED融入式需要独立事务用REQUIRES_NEW需要局部回滚用NESTED掌握这些日常开发中的事务场景基本都能应对。