Spring的事务传播机制看这一篇就够了

一、前言

spring事务传播机制对应java web应用来说是比较常见的一个方面了,在项目开发或者面试中经常会遇到,希望本篇文章对spring事务有一定基础的同学能够加深对spring事务传播机制的理解。

spring事务是在数据库事务的基础上进行封装扩展,其主要特性如下:

支持原有的数据事务的隔离级别

加入了事务传播的概念,提供多个事务的合并和隔离的功能

提供声明式事务,让业务代码与事务分离,事务更易用

spring提供了三个接口用来使用事务:

TransactionDefinition :事务定义(规定了7种类型的事务传播行为)

PlatformTransactionManager :事务的管理(事务提交,回滚)

TransactionStatus : 事务运行状态(状态判断)

TransactionDefinition.java

package org.springframework.transaction;

public interface TransactionDefinition {

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    int ISOLATION_DEFAULT = -1;

    int ISOLATION_READ_UNCOMMITTED = 1;

    int ISOLATION_READ_COMMITTED = 2;

    int ISOLATION_REPEATABLE_READ = 4;

    int ISOLATION_SERIALIZABLE = 8;

    int TIMEOUT_DEFAULT = -1;

    int getPropagationBehavior();

    int getIsolationLevel();

    int getTimeout();

    boolean isReadOnly();

    @org.springframework.lang.Nullable

    java.lang.String getName();

}

PlatformTransactionManager.java

package org.springframework.transaction;

public interface PlatformTransactionManager {

    org.springframework.transaction.TransactionStatus getTransaction(@org.springframework.lang.Nullable org.springframework.transaction.TransactionDefinition transactionDefinition) throws org.springframework.transaction.TransactionException;

    void commit(org.springframework.transaction.TransactionStatus transactionStatus) throws org.springframework.transaction.TransactionException;

    void rollback(org.springframework.transaction.TransactionStatus transactionStatus) throws org.springframework.transaction.TransactionException;

}

TransactionStatus.java

package org.springframework.transaction;

public interface TransactionStatus extends org.springframework.transaction.SavepointManager, java.io.Flushable {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

二、基础概念

什么是事务传播行为?

事务传播行为就是用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时刻事务如何传播。

下面用伪代码说明:

public class Demo1 {

public void methodA(){

    //doSomething

    methodB();

}

}

public class Demo2 {

@Transaction(Propagation=XXX)

public void methodB(){

    //doSomething

}

}

其中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置而决定。

三、为什么会有事务传播机制?

spring 对事务的控制,是使用 aop 切面实现的,我们不用关心事务的开始,提交 ,回滚,只需要在方法上加 @Transactional 注解,这时候就有问题了。

场景一: methodA方法调用了 methodB 方法,但两个方法都有事务,这个时候如果 methodB 方法异常,是让 methodB 方法提交,还是两个一起回滚。

场景二:methodA方法调用了 methodB 方法,但是只有 methodA 方法加了事务,是否把 methodB 也加入 methodA 的事务,如果 methodB 异常,是否回滚 methodA。场景三:methodA方法调用了 methodB 方法,两者都有事务,methodB 已经正常执行完,但 methodA异常,是否需要回滚 serviceB 的数据。

四、传播机制的生效条件

因为 spring 是使用 aop 来代理事务控制 ,是针对于接口或类的,所以在同一个 service 类中两个方法的调用,传播机制是不生效的。

public class DemoClass {

public void methodA(){

    //doSomething

    methodB();

}

@Transaction(Propagation=XXX)

public void methodB(){

    //doSomething

}

}

1、如果在methodA中加入:@Transactional,mehodB不加@Transactional,那么调用methodA,methodA的事物会生效,因为methodA默认的propagation为PROPAGATION_REQUIRED,此时methodB会加入到methodA中

2、如果methodA与methodB都加上@Transactional,那么调用methodA,methodA的事物会生效,此时和 1 中情况一样,事物会生效,也是由于methodB的事物会加入到methodA中,但是,其实methodA中通过this.methodB()的调用是没有触发spring 的事物,原因会统一讲解。

3、如果methodA配置@Transactional,methodB配置@Transactional( propagation = Propagation.REQUIRES_NEW ),那么调用methodA,如果methodA出现异常,根据spring的事物传播行为,其实methodB应该是入库才对,但是我测试发现根本没有生效,一样是全部回滚。

以上结果的原因解释:

spring的事物管理通过AOP代理来实现, 根据aop的思想,不可能在具体类Demo上直接处理事物,而是通过代理类来处理,代理类在调用具体类的方法来实现,根据上面的情景methodA通过this调用methodB,那么此时相当于调用methodB时是没有经过代理类的调用,因此spring无法对事物的传播行为做处理。

因此下面的类型都是针对于被调用方法来说的,理解起来要想象成两个 不同的service 的方法的调用才可以。

五、七种传播机制

Propagation.java

package org.springframework.transaction.annotation;

public enum Propagation {

    REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED;

    private final int value;

    private Propagation(int value) { /* compiled code */ }

    public int value() { /* compiled code */ }

}

类别事务传播类型传播行为说明

支持当前事务REQUIRED(必须)如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中合并成一个事务。这是最常见的选择(默认)。

支持当前事务SUPPORTS(支持)支持当前事务有则加入当前事务,如果当前没有事务,就以非事务方式执行。

支持当前事务MANDATORY(强制)使用当前的事务加入当前事务,如果当前无事务,则抛出异常,也即父级方法必须有事务。

不支持当前事务REQUIRES_NEW(隔离)新建事务,如果当前存在事务,把当前事务挂起。这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交。

不支持当前事务NOT_SUPPORTED(不支持)以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

不支持当前事务NEVER(强制非事务)以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务。

嵌套事务NESTED(嵌套事务)如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交;

如果当前没有事务,则新建事务 ;

如果它异常,父级可以捕获它的异常而不进行回滚,父级正常提交,而它回滚事务不提交 ;

但如果父级异常,它必然回滚,这就是和 REQUIRES_NEW 的区别。

说明:一般用得比较多的是 PROPAGATION_REQUIRED , REQUIRES_NEW;REQUIRES_NEW 一般用在子方法需要单独事务。

六、项目中如何使用

下面我们采用实际的项目案例,验证以上spring的事务传播机制

创建一个maven 项目spring-propagation

POM.xml

   

        org.springframework.boot

        spring-boot-starter-parent

        2.0.5.RELEASE

       

   

    com.veromca.transaction

    spring-propagation

    1.0-SNAPSHOT

   

        1.8

   

   

       

            org.mybatis.spring.boot

            mybatis-spring-boot-starter

            2.1.0

       

       

            org.projectlombok

            lombok

            true

       

       

            org.springframework.boot

            spring-boot-starter-test

            test

       

       

            org.apache.commons

            commons-lang3

       

       

            commons-io

            commons-io

            2.6

       

       

            com.alibaba

            fastjson

            1.2.30

       

       

            mysql

            mysql-connector-java

       

       

            tk.mybatis

            mapper-spring-boot-starter

            2.1.5

       

   

创建两张表

sys_user为主表,account为字表

CREATE TABLE `sys_user` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',

  `user_name` varchar(30) NULL COMMENT '用户名',

  `age` int(2) NULL COMMENT '年龄',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';

CREATE TABLE `account` (

  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',

  `user_name` varchar(30) NULL COMMENT '用户名',

  `account_no` varchar(30) NULL COMMENT '账户编号',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='账户表';

创建对应服务类

PropagationAService.java 

/**

    * 测试事务传播机制

    * 可修改调用方法来查看不同的效果

    */

    @Transactional

    public void transactionAll(SysUserPO sysUser, List accounts){

        sysUserMapper.insert(sysUser);

        //propagationBService.insertAccountsTransactionRequired(accounts);

        //propagationBService.insertAccountsTransactionRequiredNew(accounts);

        //propagationBService.insertAccountsTransactionNotSupport(accounts);

        //propagationBService.insertAccountsTransactionNever(accounts);

        //propagationBService.insertAccountsTransactionMandatory(accounts);

        //propagationBService.insertAccountsTransactionSupport(accounts);

        try {

            propagationBService.insertAccountsTransactionNested(accounts);

        }catch (Exception e){

            e.printStackTrace();

        }

        if(true) throw new IllegalArgumentException("出异常了");

    }

PropagationBService.java

@Transactional(propagation = Propagation.REQUIRED)

    public void insertAccountsTransactionRequired(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

        }

    }

@Transactional(propagation = Propagation.REQUIRES_NEW)

    public void insertAccountsTransactionRequiredNew(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

        }

    }

@Transactional(propagation = Propagation.SUPPORTS)

    public void insertAccountsTransactionSupport(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

            if (true) throw new IllegalArgumentException("出异常了");

        }

    }

@Transactional(propagation = Propagation.NOT_SUPPORTED)

    public void insertAccountsTransactionNotSupport(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

            if (true) throw new IllegalArgumentException("出异常了");

        }

    }

@Transactional(propagation = Propagation.MANDATORY)

    public void insertAccountsTransactionMandatory(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

            if (true) throw new IllegalArgumentException("出异常了");

        }

    }

@Transactional(propagation = Propagation.NEVER)

    public void insertAccountsTransactionNever(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

            if (true) throw new IllegalArgumentException("出异常了");

        }

    }

@Transactional(propagation = Propagation.NESTED)

    public void insertAccountsTransactionNested(List accounts) {

        for (AccountPO account : accounts) {

            accountMapper.insert(account);

            if (true) throw new IllegalArgumentException("出异常了");

        }

    }

测试结果:

具体测试过程大家可以通过下载本文对应源码亲自测试,加深理解。

源码链接:https://gitee.com/veromca/spring-propagation.git

值得注意的是:

测试NEVER 时当前存在事务抛出的异常信息如下(即父级方法必须无事务):

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

测试MANDATORY 时当前不存在事务抛出的异常信息如下(即父级方法必须有事务):

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

你可能感兴趣的:(Spring的事务传播机制看这一篇就够了)