Spring 事务的实现要点(高层)
Taimili 艾米莉 ( 一款免费开源的 taimili.com )
艾米莉 是一款优雅便捷的 GitHub Star 管理和提升工具,基于 PHP & javascript 构建, 能对github 的 star fork follow watch 管理和提升,最适合github 的深度用户
- Spring 的声明式事务(
@Transactional)通过 AOP(事务拦截器)实现:对标注方法创建代理,代理在方法执行前开启/获取事务,方法执行后提交或回滚。 - 事务管理核心类:
PlatformTransactionManager(接口),常见实现:DataSourceTransactionManager(JDBC)、JpaTransactionManager(JPA/Hibernate)、JtaTransactionManager(分布式/容器事务)。 - 事务资源通过
TransactionSynchronizationManager绑定到当前线程(thread-bound)。
事务属性(TransactionDefinition)常用项
- propagation:传播行为(下面详细)。
- isolation:隔离级别(READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)。
- readOnly:优化提示,通常对写操作无效。
- timeout:超时时间。
- rollbackFor / noRollbackFor:指定回滚/不回滚的异常类型。
事务回滚规则(默认)
- 默认仅对 RuntimeException(unchecked)或 Error 回滚。
- 对 checked exception(受检异常)默认不回滚。若需回滚 checked exception,需
@Transactional(rollbackFor = Exception.class)或指定具体异常。
传播行为(Propagation)详解(关键)
Spring 中 Propagation 常用枚举及语义:
- REQUIRED(默认)
-
- 如果存在当前事务:加入该事务(使用同一个事务)。
- 如果不存在:创建新事务。
- 示例:A(tx REQUIRED) 调用 B(tx REQUIRED) → A 与 B 在同一事务,B 抛出运行时异常会导致整个事务回滚(包括 A 的修改)。
- REQUIRES_NEW
-
- 总是挂起当前事务(如果存在),并创建一个新事务;方法结束后,恢复挂起事务。
- 示例:A(tx REQUIRED) 调用 B(tx REQUIRES_NEW) → B 在独立事务,B 的回滚不会影响 A(除非 A 自身也回滚或捕获/处理异常逻辑影响)。
- 注意开销:会执行事务挂起/恢复,成本较高。
- SUPPORTS
-
- 如果存在事务则加入;如果没有则以非事务方式运行(不创建新事物)。
- 适合既可在事务内工作,也可在事务外工作的场景。
- NOT_SUPPORTED
-
- 总是以非事务方式运行;如果当前存在事务,则先挂起当前事务。
- 适用于必须在无事务上下文中执行的逻辑(例如某些外部调用不能在事务中执行)。
- MANDATORY
-
- 必须在已有事务中执行;如果没有事务则抛异常(
IllegalTransactionStateException)。 - 用于强制要求事物的内部方法。
- 必须在已有事务中执行;如果没有事务则抛异常(
- NEVER
-
- 必须在无事务中运行;如果存在事务则抛异常。
- NESTED
-
- 如果存在当前事务:在物理事务内部使用 savepoint(保存点) ,可以局部回滚到保存点而不影响外层事务(前提:底层 JDBC 驱动和 DataSourceTransactionManager 支持)。
- 如果没有当前事务:行为同
REQUIRED(创建新事务)。 - 注意:
NESTED依赖 JDBC 的 savepoint 支持,因此在 JTA 或某些事务管理器下可能不可用或行为不同。
典型场景示例
代码:
1
@Servicepublic class OuterService { @Autowired InnerService inner; @Transactional // propagation = REQUIRED public void outer() { // DB 操作 A inner.requiresNew(); // REQUIRES_NEW // DB 操作 B }}@Servicepublic class InnerService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void requiresNew() { // DB 操作 C throw new RuntimeException("fail inner"); // 回滚 inner 事务,不影响 outer 的已提交动作(除非 outer 回滚) }}
行为说明:
inner.requiresNew()在独立事务中;throw导致 inner 的事务回滚。- outer 的事务如果捕获异常并继续提交,则 outer 提交(inner 的回滚不影响 outer)。
- 若 inner 使用
REQUIRED,则 inner 的异常会导致 outer 的整个事务回滚(因为是同一个事务)。
关于 NESTED 的补充
NESTED在同一个物理事务上使用 savepoint,因此 inner 抛异常可以回滚到 savepoint 而不回滚整个物理事务,前提是使用DataSourceTransactionManager且底层 JDBC 支持 savepoint。
@Transactional 的实现细节(与 AOP 关系)
@Transactional是通过TransactionalAnnotationParser解析TransactionAttribute,由TransactionInterceptor(MethodInterceptor)在代理处拦截。拦截器调用PlatformTransactionManager.getTransaction(), 然后proceed(),最后根据异常决定commit()或rollback()。- 因为基于代理:同类内部方法调用(self-invocation)不会触发事务。解决办法同 AOP:外部代理调用或使用 AspectJ。
常见事务陷阱与 Best Practices
- 不要把 @Transactional 放在 private/final 方法或类上(代理拦截不了);一般放在
public方法和 service 层。 - 自调用(self-invocation)不会走事务,把事务边界放在不同 bean 的方法上或使用 AspectJ。
- 异常回滚规则:默认仅对运行时异常回滚,需对受检异常回滚时指定
rollbackFor。 - 事务不要跨越线程边界(事务是线程绑定的)。如果需要异步/多线程,请在新线程中重新打开事务或用消息队列设计。
- 事务粒度要小:避免在事务中做网络 I/O、文件上传、长时间计算,降低锁竞争与阻塞时间。
- 尽量在 Service 层控制事务,DAO 层只做细粒度 DB 操作。
常见问题速查(Cheat sheet)
@Transactional不生效?常见原因:a) 方法为 private 或内部自调用;b) 注解在类的私有方法或 final 类;c) 代理类型问题(接口 vs 类);d) 事务管理器未配置或 Bean 未被 Spring 管理。- 要回滚 checked exception?
@Transactional(rollbackFor = Exception.class)。 - 要在内部方法使用事务但避免 self-invocation?把内部方法抽到另一个
@Servicebean。 - 想用真正的编译/类加载器织入 AOP(可拦截构造函数、自调用等)?使用 AspectJ(需要 aop.xml / LTW 或编译时织入)。
小结(要记住的关键点)
- IoC:容器负责实例化和注入依赖,推荐 构造器注入。
- AOP(Spring) :基于代理、运行时织入、适合方法级拦截,注意自调用与 final/private 的限制。
- Bean 生命周期:BeanDefinition → 实例化 → 属性注入 → Aware 回调 → postProcessBefore → 初始化方法 → postProcessAfter → 可用;销毁有对应回调。
- 事务传播:
REQUIRED(默认)与REQUIRES_NEW是最常用;理解“加入现有事务/挂起并新建事务/非事务运行”的区别非常重要。 - 事务与 AOP 密切相关:
@Transactional的实现就是用 AOP 拦截器,所有 AOP 的代理限制同样适用于事务。