無料の本の配達:juejin.cn/post/712095…
私は多くの人にインタビューしましたが、ほとんどの人がトランザクションの4つの特性と分離レベルについて話すことができますが、Springの伝播動作について尋ねると、基本的に1、2、3とは言えません。
トランザクションが成功するか失敗するかは誰もが知っています。しかし、複数のトランザクションが協力して複雑なタスクを完了する場合、このように全面的に横断することは容易ではありません。タスク間の関係に応じて、どのタスクを一緒にロールバックする必要があるか、およびどのタスクが失敗しても他のタスクに影響を与えないかを指定する必要があります。この問題を解決するには、トランザクションの伝播動作を理解する必要があります。次の表に示すように、Springには7つのトランザクション伝播動作があります。
トランザクション伝播動作タイプ | 説明する |
---|---|
PROPAGATION_REQUIRED | 現在のトランザクションがない場合は、新しいトランザクションを作成します。すでにトランザクションがある場合は、トランザクションに参加します。これが最も一般的な選択です。 |
PROPAGATION_SUPPORTS | 現在のトランザクションをサポートします。現在のトランザクションがない場合は、非トランザクションモードで実行されます。 |
PROPAGATION_MANDATORY | 現在のトランザクションを使用するか、現在のトランザクションがない場合は例外をスローします。 |
PROPAGATION_REQUIRES_NEW | 新しいトランザクションを作成します。現在のトランザクションがある場合は、現在のトランザクションを一時停止します。 |
PROPAGATION_NOT_SUPPORTED | 非トランザクション方式で操作を実行し、現在のトランザクションがある場合は現在のトランザクションを一時停止します。 |
PROPAGATION_NEVER | 非トランザクションで実行し、トランザクションが現在存在する場合は例外をスローします。 |
PROPAGATION_NESTED | トランザクションが現在存在する場合は、ネストされたトランザクション内で実行します。現在のトランザクションがない場合は、PROPAGATION_REQUIREDのようなことを行います。 |
Springは、@Transactionalアノテーションのpropagation属性を介してさまざまな伝播動作戦略を設定できます。Springは、このための列挙型クラスPropagationを提供します。ソースコードは次のとおりです。
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
/**
* 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,
* 否则新建一个事务运行内部方法
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* 支持事务,如果当前存在事务,就沿用当前事务,
* 如果不存在,则继续采用无事务的方式运行内部方法
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* 必须使用事务,如果当前没有事务,则会抛出异常,
* 如果存在当前事务,则沿用当前事务
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* 无论当前事务是否存在,都会创建新事务运行方法,
* 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 不支持事务,当前存在事务时,将挂起事务,运行方法
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* 在当前方法调用内部方法时,如果内部方法发生异常,
* 只回滚内部方法执行过的 SQL ,而不回滚当前方法的事务
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
......
}
接下来我们通过对其中三种最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略进行对比来更深入的理解。以下测试均在外部方法开启事务的情况下进行,因为在外部没有事务的情况下,三者都会新建事务,效果一样。
REQUIRED
当内部方法的事务传播行为设置为 REQUIRED 时,内部方法会加入外部方法的事务。我们在 UserServiceImpl 中添加如下方法:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addWithRequired(User user) {
mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addWithRequiredAndException(User user) {
mapper.insert(user);
throw new RuntimeException();
}
}
创建 TransactionServiceImpl 类,并添加如下方法:
@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
@Autowired
private UserService userService;
@Override
public void noTransaction_required_required_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequired(xiaoJing);
throw new RuntimeException();
}
@Override
public void noTransaction_required_requiredException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredAndException(xiaoJing);
}
@Override
@Transactional
public void transaction_required_required_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequired(xiaoJing);
throw new RuntimeException();
}
@Override
@Transactional
public void transaction_required_requiredException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredAndException(xiaoJing);
}
@Override
@Transactional
public void transaction_required_requiredException_try() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithRequired(xiaoShui);
try {
userService.addWithRequiredAndException(xiaoJing);
} catch (Exception e) {
log.error("发生异常,事务回滚!");
}
}
}
结果分析如下表所示:
方法 | 结果 | 分析 |
---|---|---|
noTransaction_required_required_externalException | 小水和小镜均成功入库 | 外部方法未开启事务,所以所有插入操作均未受到外部异常影响 |
noTransaction_required_requiredException | 小水入库,小镜未入库 | 外部方法未开启事务,内部方法事务各自独立,互不影响,「小镜」的插入方法发生异常回滚,但「小水」的插入方法不受影响 |
transaction_required_required_externalException | 小水和小镜均未入库 | 外部方法开启事务,所有内部方法均加入外部方法的事务中。而外部方法发生异常,所以导致所有操作都发生回滚 |
transaction_required_requiredException | 小水和小镜均未入库 | 外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,所以导致所有操作都发生回滚 |
transaction_required_requiredException_try | 小水和小镜均未入库 | 外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,即使发生异常的部分被 try-catch 住,所有操作仍然会回滚 |
前面四种情况都比较好理解,很多人不能理解最后一种情况:我都 try-catch 了你还想怎样?这里的关键点在于所有方法都处于同一个事务中,此时「小镜」的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。因为异常被 catch 了,所以外部方法执行完要进行 commit 操作,这时却发现当前事务已经处于 rollback 状态了,虽然它不知道哪里出了问题,但也只能听从指挥,回滚所有操作了。
PS:由于外部方法不开启事务的情况,在每种传播行为下结果都是类似的,所以后面不再给出示例。
REQUIRES_NEW
当内部方法的传播行为设置为 REQUIRES_NEW 时,内部方法会先将外部方法的事务挂起,然后开启一个新的事务 。在 UserServiceImpl 中添加如下方法:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
...
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addWithRequiredNew(User user) {
mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addWithRequiredNewAndException(User user) {
mapper.insert(user);
throw new RuntimeException();
}
}
在 TransactionServiceImpl 中添加如下方法:
@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
...
@Override
@Transactional
public void transaction_required_requiredNew_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredNew(xiaoJing);
throw new RuntimeException();
}
@Override
@Transactional
public void transaction_required_requiredNew_requiredNewException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
User shuiJing = new User().setName("水镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredNew(xiaoJing);
userService.addWithRequiredNewAndException(shuiJing);
}
@Override
@Transactional
public void transaction_required_requiredNewException_try() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
User shuiJing = new User().setName("水镜");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredNew(xiaoJing);
try {
userService.addWithRequiredNewAndException(shuiJing);
} catch (Exception e) {
log.error("发生异常,事务回滚!");
}
}
}
结果分析如下表所示:
方法 | 结果 | 分析 |
---|---|---|
transaction_required_requiredNew_externalException | 小水未入库,小镜入库 | 外部方法开启事务,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法开启一个独立的新事务,不受外部方法异常的影响 |
transaction_required_requiredNew_requiredNewException | 小水未入库,小镜入库,水镜未入库 | 外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法也会开启一个独立的新事务,因此不会受到任何方法的影响,成功入库 |
transaction_required_requiredNewException_try | 小水和小镜入库,水镜未入库 | 外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库 |
NESTED
当内部方法的传播行为设置为 NESTED 时,内部方法会开启一个新的嵌套事务(子事务)。在 UserServiceImpl 中添加如下方法:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
...
@Override
@Transactional(propagation = Propagation.NESTED)
public void addWithNested(User user) {
mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void addWithNestedAndException(User user) {
mapper.insert(user);
throw new RuntimeException();
}
}
在 TransactionServiceImpl 中添加如下方法:
@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
...
@Override
@Transactional
public void transaction_nested_nested_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithNested(xiaoShui);
userService.addWithNested(xiaoJing);
throw new RuntimeException();
}
@Override
@Transactional
public void transaction_nested_nestedException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
userService.addWithNested(xiaoShui);
userService.addWithNestedAndException(xiaoJing);
}
@Override
@Transactional
public void transaction_nested_nestedException_try() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小镜");
User shuiJing = new User().setName("水镜");
userService.addWithRequired(xiaoShui);
userService.addWithNested(xiaoJing);
try {
userService.addWithNestedAndException(shuiJing);
} catch (Exception e) {
log.error("发生异常,事务回滚!",e);
}
}
}
结果分析如下表所示:
方法 | 结果 | 分析 |
---|---|---|
transaction_nested_nested_externalException | 小水和小镜均未入库 | 外部方法开启事务,内部方法开启各自的子事务,外部方法发生异常,主事务回滚,子事务跟随主事务回滚 |
transaction_nested_nestedException | 小水和小镜均未入库 | 外部方法开启事务,内部方法开启各自的子事务,「小镜」的插入方法发生异常回滚自己的子事务;「小镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法在外部方法的子事务中,所以跟随主事务回滚 |
transaction_nested_nestedException_try | 小水和小镜入库,水镜未入库 | 外部方法开启事务,「小镜」和「水镜」开启各自的子事务,「小水」加入外部方法的事务。「水镜」的插入方法发生异常回滚自己的子事务;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库 |
每个 NESTED 事务执行前会将当前操作保存下来,叫做 savepoint (保存点),如果当前 NESTED 事务执行失败,则回滚到之前的保存点,保存点使得子事务的回滚不对主事务造成影响。NESTED 事务在外部事务提交以后自己才会提交。
总结
REQUIRES_NEW 最为简单,不管当前有无事务,它都会开启一个全新事务,既不影响外部事务,也不会影响其他内部事务,真正的井水不犯河水,坚定而独立。
外部トランザクションがない場合、 REQUIREDは独立した新しいトランザクションを開き、同じレベルの他のトランザクションに影響を与えません。外部トランザクションがある場合、外部トランザクションとともに存続し、消滅します。
NESTEDは、外部トランザクションがない場合はREQUIREDと同じ効果があります。外部トランザクションが存在する場合、外部トランザクションがロールバックすると、ネストされたトランザクション(サブトランザクション)が作成されます。外部トランザクションがロールバックされると、サブトランザクションはロールバックされますが、サブトランザクションのロールバックは、外部トランザクションおよび同じレベルの他のトランザクションには影響しません。
私の新しい本「 SpringBootFunPracticalLessons」のより排他的でエキサイティングなコンテンツ。