Spring 事务

本文最后更新于 2025年9月18日 下午

事物的特性(ACID)

  • Atomic 原子性 :事务的执行是从一而终,不可分割的,要么就都执行完成,要么就都不执行。
  • Consistency 一致性 :执行事务应当保证数据一致。比如转账。
  • Isolation 隔离性 : 多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • Durability 持久性 : 事务提交后,其对数据库的修改就是永久性的,即使系统发生故障也不会丢失。

Spring 提供了多种方式进行事务管理,但我们平时用的最多的就是使用@Transactional注解。

@Transactional

我们先点进这个注解的原始代码看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";

@AliasFor("value")
String transactionManager() default "";

String[] label() default {};

Propagation propagation() default Propagation.REQUIRED;

Isolation isolation() default Isolation.DEFAULT;

int timeout() default -1;

String timeoutString() default "";

boolean readOnly() default false;

Class<? extends Throwable>[] rollbackFor() default {};

String[] rollbackForClassName() default {};

Class<? extends Throwable>[] noRollbackFor() default {};

String[] noRollbackForClassName() default {};
}

这些参数中,我们实际上需要关注的只有:

  • propagation:事务传播行为设置
  • isolation:事务隔离级别设置
  • timeout:事务超时属性
  • readOnly:事务只读属性
  • rollbackFor / noRollbackFor:事务回滚规则

Propagation 事务传播行为

事务传播问题,指的是一个事务中有其他事务被声明且运行时,如果对其他事务的行为进行管理的问题。

考虑一个简单的例子:假设有一个完整的流程,其中有

  1. TransactionA()
  2. TransactionB()
  3. TransactionC()
  4. ...

假如我们希望这个流程是一个完整的,不容分割的事务,说明要么所有内部流程都成功完成了,要么所有流程都不会被执行。又或者我们希望内部的事务是互相独立的,一个事务的错误并不会对另一个事务产生影响。这时候,就需要考虑到事务传播行为的特征了。

这里给一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 外部方法
// @Transactional(...) Root
void process(){
// 内部方法
TransactionA();
TransactionB();
TransactionC();
...
}

// @Transactional(...) A
void TransactionA(){
...
}

// @Transactional(...) B
void TransactionB(){
...
}

// @Transactional(...) C
void TransactionC(){
...
}

在所有的传播行为设定中,我们用的最多的可能是如下几个:

  • TransactionDefinition.PROPAGATION_REQUIRED
    • 外部方法没有开启事务:内部方法开启独立的新事务。
    • 外部方法开启了事务:内部方法加入外部方法的事务。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW
    • 不论外部方法有没有开启事务,内部方法都会开启独立的新事物。
  • TransactionDefinition.PROPAGATION_NESTED
    • 外部方法没有开启事务:等同于PROPAGATION_REQUIRED
    • 外部方法开启了事务:将内部方法的事务作为外部事务的嵌套事务执行。

判断一个事务是否完成,最简单的方法就是看这个事务中是否发生了错误或异常。

这里给一个很Tricky的例子:

1
2
3
4
5
6
7
8
9
10
@Transactional(propagation = Propagation.REQUIRED)
public void process(){
TransactionA(); // Propagation.REQUIRED
TransactionB(); // Propagation.REQUIRES_NEW
try{
TransactionC(); // Propagation.REQUIRES_NEW with Exception
} catch (Exception e){
// handle exception ...
}
}

在这个情况下,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来决定遇到什么异常时不发生回滚。


Spring 事务
http://example.com/2025/09/15/Spring学习/Spring 事务/
作者
Clain Chen
发布于
2025年9月15日
许可协议