事务传播类型及简单阐释

342 阅读4分钟

事务传播介绍

事务传播方式是为了解决方法在嵌套执行的时候事务对方法的支持

  我们知道事务可以有效保证数据的一致性,不会发生转钱时突然停电,你账户里的钱少了,但是目标账户里的钱却没有增加的情况 (这里,你账户的钱减少,目标账户的钱增加,在同一个事务里),但是如果所有方法都以事务的方式执行的话,又会大大降低数据库的性能,所以我们只对进行数据更改相关的操作(插入、更新、删除)以事务方式执行,以保证数据的一致性。而数据库最常用的操作——查询,则不以事务的方式执行。

  但是,如果一个支持事务的方法如果嵌套了另外的可能支持事务的方法,它们该怎么运行呢?(换句话说:如果当前方法已经有事务了,但是这个方法中又调用了其他方法,它们可能也支持事务,那么数据库该如何处理这些方法的执行?)

事务传播就是用来解决这个问题的。

事务传播类型及说明:

事务传播类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到这个事务当中,最为常用
PROPAGATION_SUPPORTS支持事务,如果当前没有事务,就以非事务的方式执行
PROPAGATION_MANDATORY使用当前事务,如果当前没有事务,则抛出异常 mandatory(强制)
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,则挂起当前事务,在新事务中执行
PROPAGATION_NOT_SUPPORTED不支持事务,如果当前存在事务,则将事务挂起
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED嵌套事务,如果当前存在事务,则在嵌套事务中执行。如果当前不存在事务,则执行与PROPAGATION_REQUIRED类似的操作

粗体部分是我们实际工作中使用最多的三种事务传播类型。也是接下来我们介绍的类型。

事务传播解释:

我们现在有个需求:执行两个方法 batchInsert_A()batchInsert_B() 进行批量插入操作,我们创建一个方法 batchInsert() 来嵌套这两个方法并执行:

@Transactional(propagation=PROPAGATION.REQUIRED)
private void batchInsert_A(){
    ... insert ...
}
@Transactional(propagation=PROPAGATION.REQUIRED)
private void batchInsert_B(){
    ... insert ...
}

@Transactional(propagation=PROPAGATION.REQUIRED)
public void batchInsert() {
    batchInsert_A();
    // throw new RuntimeException("运行时异常");
    batchInsert_B();
}

PROPAGATION_REQUIRED:

这里三个方法事务传播类型都是 PROPAGATION_REQUIRED

第一步:执行 batchInsert() 方法的时候,它的事务传播类型配置为 PROPAGATION_REQUIRED,由于当前没有事务,所以会新建一个事务,让 batchInsert() 在这个事务中执行,

第二步:执行 batchInsert_A() 的时候,它的事务传播类型配置为 PROPAGATION_REQUIRED ,由于当前已经有事务了,所以此方法会添加到 batchInsert() 创建的事务中一起执行,

第三步:同理,batchInsert_B() 也会添加到这个事务中一起执行。由于三个方法同处于一个事务中,如果在执行过程中出现运行时异常,在当前事务中的方法所做的操作就会被数据库一起回滚。

image-20210831231437095.png image-20210831231615259.png image-20210831231647505.png

PROPAGATION_REQUIRES_NEW:

将AB两个方法的事务传播类型改为 PROPAGATION_REQUIRES_NEWbatchInsert() 可改可不改

第一步:当batchInsert() 执行的时候,新建一个事务1,并将batchInsert()添加到当前事务1中执行。

第二步:执行batchInsert_A()的时候,因为此方法的事务传播类型配置为 PROPAGATION_REQUIRES_NEW,所以它会将事务1先挂起,新建一个事务2,batchInsert_A()添加到事务2中执行,执行完毕之后,事务2进行提交事务

第三步:同理,执行batchInsert_B()时也会将事务2挂起,新建一个事务3,batchInsert_B() 在事务3中执行,执行完毕,事务3提交,最后恢复事务1,等batchInsert() 执行完毕,进行提交

因为事务之间相互独立(在事务隔离级别较高的时候,MySQL INNODB执行引擎 默认为 repeatable_read),所以在发生运行时异常的时候,已提交的事务不会进行回滚,(正在执行的事务会被回滚,还没执行的方法也不会继续执行下去了)

image-20210901155207588.png

image-20210901154404397.png

image-20210901154652994.png

PROPAGATION_NOT_SUPPORTED:

batchInsert()的事务传播类型改为 PROPAGATION_REQUIRED, batchInsert_A() batchInsert_B()的事务传播类型改为 PROPAGATION_NOT_SUPPORTED

第一步:新建一个事务1,并将batchInsert()添加进事务1中执行

第二步:因为 batchInsert_A() 的事务传播类型为 PROPAGATION_NOT_SUPPORTED,不支持事务,所以执行到batchInsert_A()时,会将事务1挂起,以非事务的方式执行

第三步:batchInsert_B()执行时,查看当前有没有事务,没有就直接执行,有就将事务挂起再执行。执行完毕后恢复事务1,等batchInsert()执行完毕之后提交事务

image-20210901155207588.png

image-20210901161539072.png

image-20210901161720249.png

总结:

相信看到这里,你已经对事务传播有了初步的认识,事务传播就是解决在方法嵌套的时候对于不同方法:要不要以事务的方式运行、以新事务的方式还是以加入已有事务的方式运行,在工作中我们应该根据我们的实际需求进行选择