Spring 的事务管理
# Spring 的事务管理
# 什么是事务
事务:逻辑上的一组操作,组成这个操作的各个单元,要么全部成功,要么全部失败
# 事务的特征
事务具有以下特性(通常使用 ACID 缩写来表示):
- 原子性(Atomicity):事务是不可分割的操作单位,要么全部成功执行,要么全部回滚。如果事务中的任何操作失败,将回滚到事务开始前的状态,不会留下部分完成的结果。
- 一致性(Consistency):事务在执行过程中要保持数据的一致性。这意味着事务的操作必须满足预定义的规则和约束,以确保数据的完整性和有效性。
- 隔离性(Isolation):事务的执行应该与其他并发事务相互隔离,使每个事务感觉就像是在独立地执行。隔离性确保并发事务之间不会互相干扰,防止数据损坏或读取脏数据。
- 持久性(Durability):一旦事务成功提交,其所做的更改应该是永久性的,即使在系统故障或崩溃之后也不会丢失。
# 如果不考虑隔离性引发的问题
# 问题
读问题:
- 脏读(Dirty Read):一个事务读取到了另一个并发事务未提交的数据,如果未提交的事务回滚,读取到的数据就是无效的。
- 不可重复读(Non-repeatable Read):一个事务在同一查询中多次读取同一行数据,但在这期间另一个并发事务对该行数据进行了修改或删除,导致读取到不一致的数据。
- 幻读(Phantom Read):一个事务在同一查询中多次读取满足某个条件的数据,但在这期间另一个并发事务插入或删除了满足该条件的数据,导致读取到了新增或删除的数据。
写问题:
- 丢失更新(Lost Update):两个或多个并发事务同时读取同一数据并进行修改,但只有最后一个提交的事务的修改结果会被保留,其他事务的修改结果会丢失。
- 脏写(Dirty Write):一个事务在未提交之前,另一个并发事务读取到了其修改但未提交的数据。如果未提交的事务回滚,读取到的数据就是无效的。
- 并发更新问题(Concurrency Update Problem):并发事务同时更新相同的数据,可能会导致数据的混乱和不一致。
# 解决
设置事务的隔离级别
- Read Uncommitted(读未提交):在这个隔离级别下,一个事务可以读取另一个事务尚未提交的数据,可能会导致脏读问题。这个隔离级别无法解决任何问题,因为它允许读取到未提交的临时数据。
- Read Committed(读已提交):在这个隔离级别下,一个事务只能读取到已经提交的数据,避免了脏读问题。然而,由于其他并发事务可能在事务执行期间提交新的数据,因此不可重复读和幻读问题仍有可能发生。
- Repeatable Read(重复读):在这个隔离级别下,一个事务在同一个查询中多次读取同一行数据时,保证每次读取到的数据是一致的,避免了不可重复读问题。但是,其他并发事务可能在事务执行期间插入新的数据或删除已有数据,导致幻读问题。
- Serializable(可串行化):在这个隔离级别下,事务串行执行,即每个事务都独占所访问的数据,避免了脏读、不可重复读和幻读问题。这是最高的隔离级别,但也是最严格的,可能导致并发性能下降。
# Spring 的传播行为
REQUIRED(默认):如果当前存在事务,则方法在该事务中运行;如果当前没有事务,则创建一个新事务并在其中运行方法。该传播行为确保方法始终在事务中运行,如果调用方法的地方没有事务,则会为方法创建新的事务。如果在外部事务中调用多个有 REQUIRED 传播行为的方法,它们将在同一个事务中运行,如果有异常抛出,将会回滚整个事务。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { // do something }
1
2
3
4
5
6
7
8
9
10- 单独调用 methodB 方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
- 调用 methodA 方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到 methodB 时,methodB 发现当前上下文有事务,因此就加入到当前事务中来。
SUPPORTS:如果当前存在事务,则方法在该事务中运行;如果当前没有事务,则方法以非事务方式运行。该传播行为允许方法在有事务的上下文中运行,也允许方法在没有事务的上下文中以非事务方式运行。它适用于读取或查询类型的操作,不需要强制要求在事务中执行。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } // 事务属性为SUPPORTS @Transactional(propagation = Propagation.SUPPORTS) public void methodB() { // do something }
1
2
3
4
5
6
7
8
9
10
11- 单纯的调用 methodB 时,methodB 方法是非事务的执行的。当调用 methdA 时,methodB 则加入了 methodA 的事务中,事务执行。
MANDATORY:如果当前存在事务,则方法在该事务中运行;如果当前没有事务,则抛出异常。该传播行为要求方法在一个已经存在的事务中运行,如果没有事务存在,则会抛出异常。它用于需要在事务上下文中执行的关键操作,确保调用方提供了有效的事务。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } // 事务属性为MANDATORY @Transactional(propagation = Propagation.MANDATORY) public void methodB() { // do something }
1
2
3
4
5
6
7
8
9
10
11- 当单独调用 methodB 时,因为当前没有一个活动的事务,则会抛出异常
throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”)
; - 当调用 methodA 时,methodB 则加入到 methodA 的事务中,事务执行。
- 当单独调用 methodB 时,因为当前没有一个活动的事务,则会抛出异常
REQUIRES_NEW:每次方法都会在自己的事务中运行。如果当前存在事务,则将其挂起,并在方法执行完毕后恢复。该传播行为要求方法在自己的事务中运行,无论当前是否已存在事务。它适用于需要独立于外部事务运行的方法,即使外部事务失败或回滚,方法的事务也会继续进行。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { doSomeThingA(); methodB(); doSomeThingB(); // do something else } // 事务属性为REQUIRES_NEW @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { // do something }
1
2
3
4
5
6
7
8
9
10
11
12
13
14- 单独执行 methodB 和使用
Propagation.REQUIRES
效果是一致的。 - 如果 methodA 方法在调用 methodB 方法后的 doSomeThingB 方法失败了,而 methodB 方法所做的结果依然被提交。而除了 methodB 之外的其它代码导致的结果却被回滚了
- 单独执行 methodB 和使用
NOT_SUPPORTED:方法以非事务方式运行,如果当前存在事务,则将其挂起。该传播行为要求方法以非事务方式运行,即使当前存在事务。它适用于不需要事务支持的操作,例如纯读取操作或与外部事务无关的方法。
用图片说明就是
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { doSomeThingA(); // 挂起事务 methodB(); // 恢复事务 doSomeThingB(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodB() { // do something }
1
2
3
4
5
6
7
8
9
10
11
12
13
14- 单独执行 methodB 就相当于没有事务
- 当执行 methodA 的时候,在执行到 methodB 方法时候会停止事务,当 methodB 方法执行完成事务恢复。
NEVER:方法以非事务方式运行,如果当前存在事务,则抛出异常。该传播行为要求方法在没有事务的上下文中运行,如果当前存在事务,则会抛出
IllegalTransactionStateException
异常。它用于确保方法不会在事务环境中运行。@Transactional(propagation = Propagation.NEVER) public void methodA() { doSomeThingA(); // 挂起事务 methodB(); // 不会发生异常 // 恢复事务 doSomeThingB(); methodC(); // 发生异常 } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodB() { // do something } @Transactional(propagation = Propagation.REQUIRED) public void methodC() { // do something }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- 这个要说明的是,在上述的情况中是不会发生异常的,因为 NOT_SUPPORTED 会在 methodB 执行的时候挂起事务。
NESTED:如果当前存在事务,则在嵌套事务中运行方法;如果当前没有事务,则创建一个新事务并在其中运行方法。嵌套事务是外部事务的一部分,具有独立的保存点,并且可以单独提交或回滚。如果外部事务回滚,嵌套事务也将回滚;如果外部事务提交,嵌套事务可以选择提交或回滚。它用于需要独立的保存点和部分提交 / 回滚的场景。
// 设置保存点和回滚 @Service @Transactional public class MyService { @Autowired private TransactionTemplate transactionTemplate; public void myMethod() { transactionTemplate.execute(status -> { try { // 执行一些事务操作 // ... // 创建保存点 Object savepoint = status.createSavepoint(); // 执行更多的事务操作 // ... if (发生某些异常条件) { // 回滚到保存点 status.rollbackToSavepoint(savepoint); } // 继续执行事务操作 // ... return null; } catch (Exception e) { // 处理异常 throw new RuntimeException("事务执行失败", e); } }); } }
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@Transactional(propagation = Propagation.REQUIRED) methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } @Transactional(propagation = Propagation.NEWSTED) methodB(){ …… }
1
2
3
4
5
6
7
8
9
10
11- 当 methodB 方法调用之前,调用 setSavepoint 方法,保存当前的状态到 savepoint。如果 methodB 方法调用失败,则恢复到之前保存的状态。
- 但是需要注意的是,这时的事务并没有进行提交,如果后续的代码 (doSomeThingB () 方法) 调用失败,则回滚包括 methodB 方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。
- 而内层事务操作失败并不会引起外层事务的回滚。
# @Transactional 注解简介
@Transactional 的所有可选属性:
- propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
- isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举 ,默认值为 Isolation.DEFAULT。
- readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
- timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。
- rollbackFor:指定需要回滚的异常类。类型为 Class [],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- rollbackForClassName:指定需要回滚的异常类类名。类型为 String [],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- noRollbackFor:指定不需要回滚的异常类。类型为 Class [],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- noRollbackForClassName: 指定不需要回滚的异常类类名。类型为 String [],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
注意
- 若 @Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解 @Transactional,虽然
Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的 @Transaction 注解。 - 若 @Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。