我的Spring声明式事务探索:常见参数与七种传播级别的记忆方式
最近在项目中频繁使用Spring的声明式事务,觉得有必要梳理一下它的核心内容。声明式事务通过注解或XML配置让事务管理变得简单,但其中的参数和传播级别总让人有点晕。这篇文章我将分享自己对Spring声明式事务常见参数的理解,以及如何记住那七种事务传播级别。
Spring声明式事务的常见参数
Spring的声明式事务主要通过@Transactional
注解来实现,注解里可以配置一些参数来控制事务的行为。以下是我总结的几个常用参数,结合实际使用的感受来聊聊它们的作用:
-
propagation(传播行为)
这是定义事务如何在方法调用链中传播的参数。Spring提供了七种传播行为(后面会详细讲),默认是Propagation.REQUIRED
。我一般会根据业务场景选择,比如嵌套方法调用时可能会用到NESTED
或REQUIRES_NEW
。 -
isolation(隔离级别)
隔离级别决定了事务在并发时如何处理数据一致性问题。Spring支持的隔离级别包括:DEFAULT
:使用数据库的默认隔离级别(我常用这个,省心)。READ_UNCOMMITTED
:允许读未提交数据,性能高但可能有脏读。READ_COMMITTED
:只读已提交数据,防止脏读,但可能有不可重复读。REPEATABLE_READ
:保证同一事务内多次读取数据一致,防止不可重复读,但可能有幻读。SERIALIZABLE
:最高隔离级别,事务串行执行,性能最低但最安全。
我在项目中一般用DEFAULT
或READ_COMMITTED
,因为数据库(如MySQL的InnoDB)默认隔离级别已经能满足大部分需求。
-
timeout(超时时间)
这个参数设置事务的超时时间(秒)。如果事务执行时间超了,Spring会抛出异常并回滚。默认是-1,意味着不设置超时,依赖数据库的配置。我在处理大数据量操作时会设置一个合理的超时,比如timeout=30
,避免事务挂起太久。 -
readOnly(只读事务)
设置为readOnly=true
时,事务只允许读操作,不能修改数据。Spring会优化只读事务的性能,比如禁用脏数据检查。我在报表查询这种只读场景下会用这个参数,能稍微提高点效率。 -
rollbackFor/noRollbackFor
rollbackFor
:指定哪些异常触发回滚,默认是RuntimeException
和Error
。如果业务中有自定义的检查型异常需要回滚,可以在这里指定,比如rollbackFor = MyCustomException.class
。noRollbackFor
:指定哪些异常不触发回滚。
我踩过一个坑:默认情况下,检查型异常(Exception
)不会触发回滚,所以得显式配置rollbackFor
。
这些参数在实际开发中挺实用,尤其是propagation
和isolation
,直接影响事务的行为。至于timeout
和readOnly
,我一般按需调整,rollbackFor
则是救命稻草,避免默认回滚规则不满足需求。
七种事务传播级别的记忆方式
Spring的事务传播级别(Propagation)有七种,分别是REQUIRED
、SUPPORTS
、MANDATORY
、REQUIRES_NEW
、NOT_SUPPORTED
、NEVER
和NESTED
。刚开始记这些的时候,我总觉得脑子不够用,后来总结了一个场景化的记忆方式,结合实际开发中的调用关系来理解,效果还不错。
记忆方式:用“饭店点餐”场景类比
我把事务传播级别想象成去饭店点餐,方法调用就像顾客和服务员的交互,事务就像点餐的订单。以下是我怎么用这个场景记住七种传播级别的:
-
REQUIRED(默认,必须有订单)
- 场景:我走进饭店点菜,如果已经有一桌(外层事务),我就加入这桌一起点菜;如果没有,我就开一桌(新事务)。
- 代码:如果当前有事务,就加入;没有就新建事务。
- 记忆:REQUIRED就像“必须有订单”,不管有没有现成的,我都要吃上饭。
-
SUPPORTS(支持现有订单)
- 场景:我去饭店,如果有桌(外层事务),我就加入点菜;如果没桌,我就不开新桌,直接吃便当(无事务)。
- 代码:如果有事务就加入,没事务就以非事务方式运行。
- 记忆:SUPPORTS是“随大流”,有订单就加入,没订单我也能凑合。
-
MANDATORY(强制要订单)
- 场景:我去饭店,必须加入现有的一桌(外层事务),如果没桌,我就生气走人(抛异常)。
- 代码:必须有外层事务,否则抛
IllegalTransactionStateException
。 - 记忆:MANDATORY是“强制要订单”,没订单我就不干了。
-
REQUIRES_NEW(必须新订单)
- 场景:我去饭店,不管有没有桌,我都要单独开一桌(新事务),原来的桌跟我无关。
- 代码:总是新建一个事务,暂停外层事务。
- 记忆:REQUIRES_NEW是“必须新订单”,我就是要独占一桌。
-
NOT_SUPPORTED(不支持订单)
- 场景:我去饭店不想跟别人拼桌(外层事务),有桌我就先把桌清空(暂停事务),然后自己吃便当(非事务)。
- 代码:如果有事务就暂停,以非事务方式运行。
- 记忆:NOT_SUPPORTED是“不支持订单”,我自带干粮,不跟你们玩。
-
NEVER(从不接受订单)
- 场景:我去饭店,如果有桌(外层事务),我扭头就走(抛异常);没桌我就安心吃便当(非事务)。
- 代码:如果有事务就抛
IllegalTransactionStateException
,否则以非事务方式运行。 - 记忆:NEVER是“从不点餐”,有订单我就不干了。
-
NESTED(嵌套订单)
- 场景:我在饭店点菜,加入现有的一桌(外层事务),但我的菜是单独一份账单(嵌套事务)。如果我这桌菜有问题,只取消我的菜,别人不受影响。
- 代码:在当前事务中嵌套一个子事务,子事务可以独立提交或回滚(通过保存点实现)。
- 记忆:NESTED是“嵌套订单”,我点菜但有自己的小账本。
小技巧:分类记忆
为了更方便记忆,我把这七种传播级别分成三类:
- 必须有事务:
REQUIRED
、MANDATORY
、NESTED
(都要求有事务环境,但处理方式不同)。 - 不需要事务:
SUPPORTS
、NOT_SUPPORTED
、NEVER
(可以无事务运行,但对现有事务的态度不同)。 - 独立事务:
REQUIRES_NEW
(总是要新的独立事务)。
用这个“饭店点餐”的场景,我基本能把七种传播级别记牢。实际开发中,REQUIRED
和REQUIRES_NEW
用得最多,NESTED
在复杂业务中偶尔会用,其他几种相对少见。