Spring 事务
本文最后更新于 2025年9月18日 下午
事物的特性(ACID)
- Atomic 原子性 :事务的执行是从一而终,不可分割的,要么就都执行完成,要么就都不执行。
- Consistency 一致性 :执行事务应当保证数据一致。比如转账。
- Isolation 隔离性 : 多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- Durability 持久性 : 事务提交后,其对数据库的修改就是永久性的,即使系统发生故障也不会丢失。
Spring 提供了多种方式进行事务管理,但我们平时用的最多的就是使用@Transactional注解。
@Transactional
我们先点进这个注解的原始代码看看
1 | |
这些参数中,我们实际上需要关注的只有:
propagation:事务传播行为设置isolation:事务隔离级别设置timeout:事务超时属性readOnly:事务只读属性rollbackFor / noRollbackFor:事务回滚规则
Propagation 事务传播行为
事务传播问题,指的是一个事务中有其他事务被声明且运行时,如果对其他事务的行为进行管理的问题。
考虑一个简单的例子:假设有一个完整的流程,其中有
TransactionA()TransactionB()TransactionC()...
假如我们希望这个流程是一个完整的,不容分割的事务,说明要么所有内部流程都成功完成了,要么所有流程都不会被执行。又或者我们希望内部的事务是互相独立的,一个事务的错误并不会对另一个事务产生影响。这时候,就需要考虑到事务传播行为的特征了。
这里给一个例子:
1 | |
在所有的传播行为设定中,我们用的最多的可能是如下几个:
TransactionDefinition.PROPAGATION_REQUIRED:- 外部方法没有开启事务:内部方法开启独立的新事务。
- 外部方法开启了事务:内部方法加入外部方法的事务。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:- 不论外部方法有没有开启事务,内部方法都会开启独立的新事物。
TransactionDefinition.PROPAGATION_NESTED- 外部方法没有开启事务:等同于
PROPAGATION_REQUIRED - 外部方法开启了事务:将内部方法的事务作为外部事务的嵌套事务执行。
- 外部方法没有开启事务:等同于
判断一个事务是否完成,最简单的方法就是看这个事务中是否发生了错误或异常。
这里给一个很Tricky的例子:
1 | |
在这个情况下,C发生了异常且被捕获处理,但最终的结果是:
- A完成了事务
- B完成了事务
- C回滚了事务
如果将C的传播设置改为REQUIRED,则会变成:
- A回滚了事务
- B完成了事务
- C回滚了事务
其实这并不难理解,如果我们分析一下两种情况中出现的所有事物及其所属关系列出来,就很明了了:
-
当C为
REQUIRES_NEW,这时候会有三个不同的事务:- 事务Root:其中包括了事务A
- 事务B
- 事务C
由于C所产生的异常被处理了,所以C的事务会被标记为回滚状态,最终会发生回滚。而其他两个则不会。
-
当C为
REQUIRED,这时候会有两个不同的事务:- 事务Root:其中包括了事务A和事务C
- 事务B
由于C产生了异常,虽然这个异常被捕获了,但是它依然会影响到自己,也同样会影响到处于同一事务下的事务A,因此,A和C都会发生回滚,而B会完成。
事务隔离级别
事务隔离级别分为五种:
-
TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的REPEATABLE_READ隔离级别 Oracle 默认采用的READ_COMMITTED隔离级别. -
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 -
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 -
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 -
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务超时,只读属性
可以给事务设置一个超时timeout属性来限制一个事务最长能够执行的时间,如果规定时间内没有完成,则自动发生回滚。默认为-1表示超时时间取决于底层事务系统或者压跟就没有超时时间。
可以给事务设置一个只读readOnly属性来标记该事务中只存在读操作。Spring主要是为这种情况优化数据库的执行效率,并不会带来什么其他的收益。在我看来,更多的可能是用来优化保证单纯的读一致性的效率。
事务回滚规则
默认情况下,事务只会遇到运行期异常(RuntimeException的子类)或 Erro时产生回滚,但是在遇到检查型异常时不会回滚。这个属性可以告诉该事务在遇到什么异常的情况下进行回滚,即便这个异常不是运行期异常意外的异常。
@Transactional(rollbackFor = MyException.class)
当然了,你也可以设定noRollbackFor来决定遇到什么异常时不发生回滚。