Spring事务传播行为全解析
好的,@Transactional
注解中的 propagation
属性是定义事务传播行为的关键。它解决了这样一个核心问题:当一个事务方法被另一个事务方法调用时,这两个方法的事务应该如何交互?
Spring 提供了 7 种传播行为,定义在 Propagation
枚举中。下面我将逐一列举并解释它们:
1. Propagation.REQUIRED
(默认值)
- 含义:如果当前存在一个事务,则加入这个事务;如果当前没有事务,则创建一个新的事务。
- 场景:这是最常用的设置,适用于大多数情况。它能保证多个操作在同一个事务中执行(例如,下单操作和扣库存操作)。
- 比喻:坐地铁。如果你已经有票(已有事务),就直接进站;如果没票(没有事务),就买一张新票(新事务)。
2. Propagation.SUPPORTS
- 含义:如果当前存在一个事务,则加入这个事务;如果当前没有事务,则以非事务的方式继续运行。
- 场景:适用于那些“可有可无”的非核心操作,比如查询。它可以适应调用方的事务环境,但不强求。
- 比喻:跟团游。如果团队有行程(已有事务),你就跟着走;如果团队是自由活动(没有事务),你就自己玩(非事务)。
3. Propagation.MANDATORY
(强制)
- 含义:必须在一个已有的事务中运行。如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- 场景:用于强制要求必须在事务上下文中被调用的方法,否则就视为编程错误。
- 比喻:你必须用公司的商务卡(事务)来报销,如果你试图用自己的个人卡(无事务)报销,财务会拒绝并报错。
4. Propagation.REQUIRES_NEW
(新建)
- 含义:无论当前是否存在事务,都会创建一个新的事务。如果当前有事务,则把当前事务挂起。
- 场景:用于需要独立提交或回滚的场景,不希望受外部事务失败的影响。例如日志记录,无论主业务是否成功,日志都必须被记录到数据库。
- 比喻:无论你现在是不是在跟团游(外部事务),你都要自己单独去办一件急事(新事务),办完后你再回来继续跟团。
5. Propagation.NOT_SUPPORTED
(不支持)
- 含义:以非事务方式执行操作。如果当前存在事务,则把当前事务挂起,等该方法执行完毕后再恢复。
- 场景:用于完全不需要事务支持的操作,比如执行某些不需要事务的数据库查询或操作。
- 比喻:你在跟团游(外部事务)过程中,突然需要去路边一个不收门票(无事务)的公共喷泉玩一会。导游(事务管理器)会等你玩完再继续行程。
6. Propagation.NEVER
(从不)
- 含义:必须在非事务环境下执行。如果当前存在事务,则抛出异常。
- 场景:用于强制要求不能在事务中被调用的方法。
- 比喻:某个活动明确禁止旅行团参加(禁止事务),只接受散客(非事务)。如果旅行团来了,就会被拒之门外(抛异常)。
7. Propagation.NESTED
(嵌套)
- 含义:如果当前存在事务,则在当前事务的一个嵌套事务中执行。如果当前没有事务,其行为与
REQUIRED
类似,会创建一个新事务。- 嵌套事务的特点:它是外部事务的一个子事务。它的提交会随外部事务一起提交,但它的回滚可以独立于外部事务(外部事务可以选择是回滚到嵌套事务前的保存点,还是回滚整个事务)。
- 场景:适用于事务中可以独立回滚的子流程。例如,用户下单时,可以先预留库存(嵌套事务),如果后续添加优惠券失败,可以只回滚添加优惠券的操作,而保留预留的库存(或者也可以选择全部回滚)。
- 注意:此传播行为需要 JDBC 3.0 以上驱动和数据库的支持(如 PostgreSQL),但有些资源管理器(如 JTA)可能不支持嵌套事务。
- 比喻:玩游戏时存档(设置保存点)。你可以尽情尝试一个高风险的任务(嵌套事务),如果失败了,你可以读档回到存档点(回滚嵌套事务),而不需要从头开始玩(回滚整个事务)。
总结与选择建议
传播行为 | 含义 | 外部事务存在 | 外部事务不存在 |
---|---|---|---|
REQUIRED (默认) | 支持当前事务,不存在则新建 | 加入 | 新建 |
SUPPORTS | 支持当前事务,不存在则以非事务运行 | 加入 | 非事务 |
MANDATORY | 强制必须在事务中运行 | 加入 | 抛异常 |
REQUIRES_NEW | 挂起当前事务,创建新事务 | 新建(挂起外部) | 新建 |
NOT_SUPPORTED | 以非事务运行,挂起当前事务 | 非事务(挂起外部) | 非事务 |
NEVER | 必须在非事务中运行 | 抛异常 | 非事务 |
NESTED | 在嵌套事务中运行 | 嵌套 | 新建 |
如何选择?
- 绝大多数情况:使用默认的
REQUIRED
即可。 - 需要独立事务(如日志记录):使用
REQUIRES_NEW
。 - 需要精细控制部分回滚:在支持的情况下,考虑使用
NESTED
。 - 强制/禁止事务环境:使用
MANDATORY
或NEVER
。