我的Spring声明式事务探索:常见参数与七种传播级别的记忆方式

最近在项目中频繁使用Spring的声明式事务,觉得有必要梳理一下它的核心内容。声明式事务通过注解或XML配置让事务管理变得简单,但其中的参数和传播级别总让人有点晕。这篇文章我将分享自己对Spring声明式事务常见参数的理解,以及如何记住那七种事务传播级别。

Spring声明式事务的常见参数

Spring的声明式事务主要通过@Transactional注解来实现,注解里可以配置一些参数来控制事务的行为。以下是我总结的几个常用参数,结合实际使用的感受来聊聊它们的作用:

  1. propagation(传播行为)
    这是定义事务如何在方法调用链中传播的参数。Spring提供了七种传播行为(后面会详细讲),默认是Propagation.REQUIRED。我一般会根据业务场景选择,比如嵌套方法调用时可能会用到NESTEDREQUIRES_NEW

  2. isolation(隔离级别)
    隔离级别决定了事务在并发时如何处理数据一致性问题。Spring支持的隔离级别包括:

    • DEFAULT:使用数据库的默认隔离级别(我常用这个,省心)。
    • READ_UNCOMMITTED:允许读未提交数据,性能高但可能有脏读。
    • READ_COMMITTED:只读已提交数据,防止脏读,但可能有不可重复读。
    • REPEATABLE_READ:保证同一事务内多次读取数据一致,防止不可重复读,但可能有幻读。
    • SERIALIZABLE:最高隔离级别,事务串行执行,性能最低但最安全。
      我在项目中一般用DEFAULTREAD_COMMITTED,因为数据库(如MySQL的InnoDB)默认隔离级别已经能满足大部分需求。
  3. timeout(超时时间)
    这个参数设置事务的超时时间(秒)。如果事务执行时间超了,Spring会抛出异常并回滚。默认是-1,意味着不设置超时,依赖数据库的配置。我在处理大数据量操作时会设置一个合理的超时,比如timeout=30,避免事务挂起太久。

  4. readOnly(只读事务)
    设置为readOnly=true时,事务只允许读操作,不能修改数据。Spring会优化只读事务的性能,比如禁用脏数据检查。我在报表查询这种只读场景下会用这个参数,能稍微提高点效率。

  5. rollbackFor/noRollbackFor

    • rollbackFor:指定哪些异常触发回滚,默认是RuntimeExceptionError。如果业务中有自定义的检查型异常需要回滚,可以在这里指定,比如rollbackFor = MyCustomException.class
    • noRollbackFor:指定哪些异常不触发回滚。
      我踩过一个坑:默认情况下,检查型异常(Exception)不会触发回滚,所以得显式配置rollbackFor

这些参数在实际开发中挺实用,尤其是propagationisolation,直接影响事务的行为。至于timeoutreadOnly,我一般按需调整,rollbackFor则是救命稻草,避免默认回滚规则不满足需求。

七种事务传播级别的记忆方式

Spring的事务传播级别(Propagation)有七种,分别是REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVERNESTED。刚开始记这些的时候,我总觉得脑子不够用,后来总结了一个场景化的记忆方式,结合实际开发中的调用关系来理解,效果还不错。

记忆方式:用“饭店点餐”场景类比

我把事务传播级别想象成去饭店点餐,方法调用就像顾客和服务员的交互,事务就像点餐的订单。以下是我怎么用这个场景记住七种传播级别的:

  1. REQUIRED(默认,必须有订单)

    • 场景:我走进饭店点菜,如果已经有一桌(外层事务),我就加入这桌一起点菜;如果没有,我就开一桌(新事务)。
    • 代码:如果当前有事务,就加入;没有就新建事务。
    • 记忆:REQUIRED就像“必须有订单”,不管有没有现成的,我都要吃上饭。
  2. SUPPORTS(支持现有订单)

    • 场景:我去饭店,如果有桌(外层事务),我就加入点菜;如果没桌,我就不开新桌,直接吃便当(无事务)。
    • 代码:如果有事务就加入,没事务就以非事务方式运行。
    • 记忆:SUPPORTS是“随大流”,有订单就加入,没订单我也能凑合。
  3. MANDATORY(强制要订单)

    • 场景:我去饭店,必须加入现有的一桌(外层事务),如果没桌,我就生气走人(抛异常)。
    • 代码:必须有外层事务,否则抛IllegalTransactionStateException
    • 记忆:MANDATORY是“强制要订单”,没订单我就不干了。
  4. REQUIRES_NEW(必须新订单)

    • 场景:我去饭店,不管有没有桌,我都要单独开一桌(新事务),原来的桌跟我无关。
    • 代码:总是新建一个事务,暂停外层事务。
    • 记忆:REQUIRES_NEW是“必须新订单”,我就是要独占一桌。
  5. NOT_SUPPORTED(不支持订单)

    • 场景:我去饭店不想跟别人拼桌(外层事务),有桌我就先把桌清空(暂停事务),然后自己吃便当(非事务)。
    • 代码:如果有事务就暂停,以非事务方式运行。
    • 记忆:NOT_SUPPORTED是“不支持订单”,我自带干粮,不跟你们玩。
  6. NEVER(从不接受订单)

    • 场景:我去饭店,如果有桌(外层事务),我扭头就走(抛异常);没桌我就安心吃便当(非事务)。
    • 代码:如果有事务就抛IllegalTransactionStateException,否则以非事务方式运行。
    • 记忆:NEVER是“从不点餐”,有订单我就不干了。
  7. NESTED(嵌套订单)

    • 场景:我在饭店点菜,加入现有的一桌(外层事务),但我的菜是单独一份账单(嵌套事务)。如果我这桌菜有问题,只取消我的菜,别人不受影响。
    • 代码:在当前事务中嵌套一个子事务,子事务可以独立提交或回滚(通过保存点实现)。
    • 记忆:NESTED是“嵌套订单”,我点菜但有自己的小账本。

小技巧:分类记忆

为了更方便记忆,我把这七种传播级别分成三类:

用这个“饭店点餐”的场景,我基本能把七种传播级别记牢。实际开发中,REQUIREDREQUIRES_NEW用得最多,NESTED在复杂业务中偶尔会用,其他几种相对少见。

总结