事务的传播行为是为了解决业务层方法之间互相调用的事务问题,当一个事务方法被另一事务方法调用时,不同的传播行为会产生不同的结果。本文将结合代码实例,探讨一下如何正确使用事务的传播行为。
说明:本篇内容基于 MySQL(8.0.30-1.el8)及 SpringBoot(3.3.5)进行测试。
事务传播行为
Spring通过Propagation
枚举类定义了7种传播行为。
代码案例
接下来我们通过代码案例充分了解一下这些传播行为的用法。
先定义两个内部服务PropagationAddUser1Service
、PropagationAddUser2Service
,用于实现数据库的插入。
@Service
public class PropagationAddUser1ServiceImpl implements PropagationAddUser1Service {
@Resource
private User1Mapper user1Mapper;
// 不使用事务插入
@Override
public void add(User1 user) {
user1Mapper.insert(user);
}
// 其他方法...
}
@Service
public class PropagationAddUser2ServiceImpl implements PropagationAddUser2Service {
@Resource
private User2Mapper user2Mapper;
// 不使用事务插入
@Override
public void add(User2 user) {
user2Mapper.insert(user);
}
// 不使用事务插入,抛出异常
@Override
public void addThrow(User2 user) {
user2Mapper.insert(user);
// 模拟异常
throw new RuntimeException();
}
// 其他方法...
}
再定义一个外部服务PropagationAddExampleImpl
,用于实现外部方法调用内部服务的方法。
@Service
public class PropagationAddExampleImpl implements PropagationAddExample {
private final PropagationAddUser1Service user1Service;
private final PropagationAddUser2Service user2Service;
@Autowired
public PropagationAddExampleImpl(PropagationAddUser1Service user1Service, PropagationAddUser2Service user2Service) {
this.user1Service = user1Service;
this.user2Service = user2Service;
}
// 外部方法出现异常
@Override
public void noEx_no_no() {
user1Service.add(createUser1());
user2Service.add(createUser2());
// 模拟异常
throw new RuntimeException();
}
// 内部方法出现异常
@Override
public void no_no_noEx() {
user1Service.add(createUser1());
user2Service.addThrow(createUser2());
}
// 其他方法...
}
最终通过调用外部服务来测试结果。
比如上述两个外部方法noEx_no_no
和no_no_noEx
调用后的结果都为成功,因为没有使用事务,所以不会触发回滚。对应的测试用例如下:
@Test
void noEx_no_no() {
assertThrows(RuntimeException.class, propagationAddExample::noEx_no_no);
assertEquals(1, getUser1Count());
assertEquals(1, getUser2Count());
}
@Test
void no_no_noEx() {
assertThrows(RuntimeException.class, propagationAddExample::no_no_noEx);
assertEquals(1, getUser1Count());
assertEquals(1, getUser2Count());
}
上述外部方法,如果添加了@Transactional
注解,即开启默认事务,此时整个外部方法将视为一个事务,出现异常会触发事务回滚,比如下方内部方法1和内部方法2产生的数据都会回滚。
// 外部方法出现异常
@Transactional
@Override
public void requiredEx_no_no() {
user1Service.add(createUser1());
user2Service.add(createUser2());
// 模拟异常
throw new RuntimeException();
}
// 内部方法出现异常
@Transactional
@Override
public void required_no_noEx() {
user1Service.add(createUser1());
user2Service.addThrow(createUser2());
}
这也是我们开发中最常见的使用方式,而通过传播行为可以实现更为复杂的业务场景。
1. REQUIRED
如果当前没有事务,就新建一个事务;如果当前存在事务,就加入到这个事务中。
我们平时使用的@Transactional
注解默认就是使用此传播行为。
@Service
public class PropagationAddUser1ServiceImpl implements PropagationAddUser1Service {
// 使用事务插入
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void addRequired(User1 user) {
user1Mapper.insert(user);
}
// 其他方法...
}
@Service
public class PropagationAddUser2ServiceImpl implements PropagationAddUser2Service {
// 使用事务插入
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void addRequired(User2 user) {
user2Mapper.insert(user);
}
// 使用事务插入,抛出异常
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void addRequiredThrow(User2 user) {
user2Mapper.insert(user);
// 模拟异常
throw new RuntimeException();
}
// 其他方法...
}
1.1 场景1:外部方法没有开启事务
1.1.1 外部方法出现异常
@Override
public void noEx_required_required() {
user1Service.addRequired(createUser1());
user2Service.addRequired(createUser2());
throw new RuntimeException();
}
1.1.2 内部方法出现异常
@Override
public void no_required_requiredEx() {
user1Service.addRequired(createUser1());
user2Service.addRequiredThrow(createUser2());
}
1.2 场景2:外部方法开启事务
1.2.1 外部方法出现异常
@Transactional
@Override
public void requiredEx_required_required() {
user1Service.addRequired(createUser1());
user2Service.addRequiredThrow(createUser2());
throw new RuntimeException();
}
1.2.2 内部方法出现异常
@Transactional
@Override
public void required_required_requiredEx() {
user1Service.addRequired(createUser1());
user2Service.addRequiredThrow(createUser2());
}
1.2.3 捕获内部方法的异常
@Transactional
@Override
public void required_required_requiredCatch() {
user1Service.addRequired(createUser1());
try {
user2Service.addRequiredThrow(createUser2());
} catch (Exception ignored) {}
}
Spring官方关于UnexpectedRollbackException
的描述:
However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException
is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So, if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException
to indicate clearly that a rollback was performed instead.
简单翻译一下:
在内部事务范围设置了仅回滚标记的情况下,外部事务本身并没有决定回滚,所以回滚(由内部事务静默触发的)是意外的,此时会抛出UnexpectedRollbackException
。因此,如果一个内部事务默默地将一个事务标记为仅回滚,外部调用者仍然会提交,此时外部调用者需要收到一个意外回滚的异常,以明确表示已执行回滚。
2. REQUIRES_NEW
如果当前没有事务,就新建一个事务;如果当前存在事务,就挂起当前事务并新建一个事务。
新建如下方法,@Transactional
注解的propagation
参数设置为Propagation.REQUIRES_NEW
。(方法实现内容不变,此处省略,后续不再赘述)
// 使用事务插入
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void addRequiresNew(User1 user) {...}
// 使用事务插入
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void addRequiresNew(User2 user) {...}
// 使用事务插入,抛出异常
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void addRequiresNewThrow(User2 user) {...}
2.1 场景1:外部方法没有开启事务
2.1.1 外部方法出现异常
@Override
public void noEx_new_new() {
user1Service.addRequiresNew(createUser1());
user2Service.addRequiresNew(createUser2());
throw new RuntimeException();
}
2.1.2 内部方法出现异常
@Override
public void no_new_newEx() {
user1Service.addRequiresNew(createUser1());
user2Service.addRequiresNewThrow(createUser2());
}
2.2 场景2:外部方法开启事务
2.2.1 外部方法出现异常
@Transactional
@Override
public void requiredEx_new_new() {
user1Service.addRequiresNew(createUser1());
user2Service.addRequiresNew(createUser2());
throw new RuntimeException();
}
2.2.2 内部方法出现异常
@Transactional
@Override
public void required_new_newEx() {
user1Service.addRequiresNew(createUser1());
user2Service.addRequiresNewThrow(createUser2());
}
2.2.3 捕获内部方法的异常
@Transactional
@Override
public void required_new_newCatch() {
user1Service.addRequiresNew(createUser1());
try {
user2Service.addRequiresNewThrow(createUser2());
} catch (Exception ignored) {}
}
2.3 注意事项
Spring官方针对
REQUIRES_NEW
的说明:The resources attached to the outer transaction will remain bound there while the inner transaction acquires its own resources such as a new database connection. This may lead to exhaustion of the connection pool and potentially to a deadlock if several threads have an active outer transaction and wait to acquire a new connection for their inner transaction, with the pool not being able to hand out any such inner connection anymore. Do not use
PROPAGATION_REQUIRES_NEW
unless your connection pool is appropriately sized, exceeding the number of concurrent threads by at least 1.
简单解释一下:
当内部事务获取自己的资源,如新的数据库连接时,外部事务的资源将保存绑定状态。也就是说,使用REQUIRES_NEW
会导致一个请求占用多个数据库连接,可能会导致连接池资源耗尽,并且可能导致死锁。除非连接池大小至少超过并发线程数,否则不要使用REQUIRES_NEW
。
3. NESTED
如果当前没有事务,就新建一个事务(类似于
REQUIRED
);如果当前存在事务,则作为当前事务的子事务执行。
新建如下方法,@Transactional
注解的propagation
参数设置为Propagation.NESTED
。
// 使用事务插入
@Transactional(propagation = Propagation.NESTED)
@Override
public void addNested(User1 user) {...}
// 使用事务插入
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void addNested(User2 user) {...}
// 使用事务插入,抛出异常
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void addNestedThrow(User2 user) {...}
3.1 场景1:外部方法没有开启事务
3.1.1 外部方法出现异常
@Override
public void noEx_nested_nested() {
user1Service.addNested(createUser1());
user2Service.addNested(createUser2());
throw new RuntimeException();
}
3.1.2 内部方法出现异常
@Override
public void no_nested_nestedEx() {
user1Service.addNested(createUser1());
user2Service.addNestedThrow(createUser2());
}
3.2 场景2:外部方法开启事务
3.2.1 外部方法出现异常
@Transactional
@Override
public void requiredEx_nested_nested() {
user1Service.addNested(createUser1());
user2Service.addNested(createUser2());
throw new RuntimeException();
}
3.2.2 内部方法出现异常
@Transactional
@Override
public void required_nested_nestedEx() {
user1Service.addNested(createUser1());
user2Service.addNestedThrow(createUser2());
}
3.2.3 捕获内部方法的异常
@Transactional
@Override
public void required_nested_nestedCatch() {
user1Service.addNested(createUser1());
try {
user2Service.addNestedThrow(createUser2());
} catch (Exception ignored) {}
}
3.3 注意事项
Spring官方针对
NESTED
的说明:
PROPAGATION_NESTED
uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks let an inner transaction scope trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This setting is typically mapped onto JDBC savepoints, so it works only with JDBC resource transactions. See Spring’s DataSourceTransactionManager.
此内容中需要注意的是,NESTED
仅适用于JDBC资源事务。
3.4 NESTED 与 REQUIRED 对比
传播行为NESTED
与REQUIRED
的测试结果有许多相似之处,但区别在于REQUIRED
是同一事务,NESTED
是嵌套事务。
换句话说,如果REQUIED
的事务出现异常,同一事务内的所有数据都会回滚;而NESTED
的父事务回滚会导致子事务回滚,子事务回滚不会导致父事务回滚(子事务的异常没有抛出给父事务就不会回滚)。
4 SUPPORTS
如果当前没有事务,就以非事务方式执行;如果当前存在事务,就加入到这个事务中。
新建如下方法,@Transactional
注解的propagation
参数设置为Propagation.SUPPORTS
。
// 使用事务插入
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public void addSupports(User1 user) {...}
// 使用事务插入
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public void addSupports(User2 user) {...}
// 使用事务插入,抛出异常
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public void addSupportsThrow(User2 user) {...}
4.1 场景1:外部方法没有开启事务
4.1.1 外部方法出现异常
@Override
public void noEx_supports_supports() {
user1Service.addSupports(createUser1());
user2Service.addSupports(createUser2());
throw new RuntimeException();
}
4.1.2 内部方法出现异常
@Override
public void no_supports_supportsEx() {
user1Service.addSupports(createUser1());
user2Service.addSupportsThrow(createUser2());
}
4.2 场景2:外部方法开启事务
4.2.1 外部方法出现异常
@Transactional
@Override
public void requiredEx_supports_supports() {
user1Service.addSupports(createUser1());
user2Service.addSupports(createUser2());
throw new RuntimeException();
}
4.2.2 内部方法出现异常
@Transactional
@Override
public void required_supports_supportsEx() {
user1Service.addSupports(createUser1());
user2Service.addSupportsThrow(createUser2());
}
4.2.3 捕获内部方法的异常
@Transactional
@Override
public void required_supports_supportsCatch() {
user1Service.addSupports(createUser1());
try {
user2Service.addSupportsThrow(createUser2());
} catch (Exception ignored) {}
}
5 MANDATORY
如果当前没有事务,就抛出异常;如果当前存在事务,就加入到这个事务中。
新建如下方法,@Transactional
注解的propagation
参数设置为Propagation.MANDATORY
。
// 使用事务插入
@Transactional(propagation = Propagation.MANDATORY)
@Override
public void addMandatory(User1 user) {...}
5.1 场景1:外部方法没有开启事务
服务 | 结果 | 分析 |
---|---|---|
服务1 | 异常 | 外部方法没有事务,直接抛出异常,不会执行 |
服务2 | 异常 | 外部方法没有事务,直接抛出异常,不会执行 |
此时抛出的异常为:org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
。
5.2 场景2:外部方法开启事务
5.2.1 外部方法出现异常
@Transactional
@Override
public void requiredEx_mandatory_mandatory() {
user1Service.addMandatory(createUser1());
user2Service.addMandatory(createUser2());
throw new RuntimeException();
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 回滚 | 外部方法存在事务,服务1将使用该事务,以该事务的规则执行,REQUIRED 的规则在此时会触发回滚 |
服务2 | 回滚 | 外部方法存在事务,服务2将使用该事务,以该事务的规则执行,REQUIRED 的规则在此时会触发回滚 |
5.2.2 内部方法出现异常
@Transactional
@Override
public void required_mandatory_mandatoryEx() {
user1Service.addMandatory(createUser1());
user2Service.addMandatoryThrow(createUser2());
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 回滚 | 外部方法存在事务,服务1将使用该事务,以该事务的规则执行,REQUIRED 的规则在此时会触发回滚 |
服务2 | 回滚 | 外部方法存在事务,服务2将使用该事务,以该事务的规则执行,REQUIRED 的规则在此时会触发回滚 |
5.2.3 捕获内部方法的异常
6 NOT_SUPPORTED
以非事务方式执行;如果当前存在事务,就挂起当前事务。
新建如下方法,@Transactional
注解的propagation
参数设置为Propagation.NOT_SUPPORTED
。
// 使用事务插入
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void addNotSupported(User1 user) {...}
// 使用事务插入
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void addNotSupported(User2 user) {...}
// 使用事务插入,抛出异常
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void addNotSupportedThrow(User2 user) {...}
6.1 场景1:外部方法没有开启事务
此时直接以非事务方式执行,不再赘述。
6.2 场景2:外部方法开启事务
6.2.1 外部方法出现异常
@Transactional
@Override
public void requiredEx_not_not() {
user1Service.addNotSupported(createUser1());
user2Service.addNotSupported(createUser2());
throw new RuntimeException();
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 成功 | 外部方法存在事务,将挂起当前事务,服务1会以非事务方式执行,不会回滚 |
服务2 | 成功 | 外部方法存在事务,将挂起当前事务,服务2会以非事务方式执行,不会回滚 |
6.2.2 内部方法出现异常
@Transactional
@Override
public void required_not_notEx() {
user1Service.addNotSupported(createUser1());
user2Service.addNotSupportedThrow(createUser2());
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 成功 | 外部方法存在事务,将挂起当前事务,服务1会以非事务方式执行,不会回滚 |
服务2 | 成功 | 外部方法存在事务,将挂起当前事务,服务1会以非事务方式执行,不会回滚 |
6.2.3 捕获内部方法的异常
7 NEVER
以非事务方式执行;如果当前存在事务,就抛出异常。
新建如下方法,@Transactional
注解的propagation
参数设置为Propagation.NEVER
。
// 使用事务插入
@Transactional(propagation = Propagation.NEVER)
@Override
public void addNever(User1 user) {...}
// 使用事务插入
@Transactional(propagation = Propagation.NEVER)
@Override
public void addNever(User2 user) {...}
// 使用事务插入,抛出异常
@Transactional(propagation = Propagation.NEVER)
@Override
public void addNeverThrow(User2 user) {...}
7.1 场景1:外部方法没有开启事务
7.1.1 外部方法出现异常
@Override
public void noEx_never_never() {
user1Service.addNever(createUser1());
user2Service.addNever(createUser2());
throw new RuntimeException();
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 成功 | 外部方法没有事务,以非事务形式执行,不会回滚 |
服务2 | 成功 | 外部方法没有事务,以非事务形式执行,不会回滚 |
7.1.2 内部方法出现异常
@Override
public void no_never_neverEx() {
user1Service.addNever(createUser1());
user2Service.addNeverThrow(createUser2());
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 成功 | 外部方法没有事务,以非事务形式执行,不会回滚 |
服务2 | 成功 | 外部方法没有事务,以非事务形式执行,不会回滚 |
7.2 场景2:外部方法开启事务
@Transactional
@Override
public void requiredEx_never_never() {
user1Service.addNever(createUser1());
user2Service.addNever(createUser2());
}
服务 | 结果 | 分析 |
---|---|---|
服务1 | 异常 | 外部存在事务,直接抛出异常,不会执行 |
服务2 | 异常 | 外部存在事务,直接抛出异常,不会执行 |
此时抛出的异常为:org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
。