Spring事务传播行为

489 阅读4分钟

基本概念

事务传播行为就是用来描述一个方法被调用时事务应该执行的动作

class A {
    public void method1() {
		
    }
}
class B {
	A a;
    public void method2() {
		a.method1();
    }
}

以上述代码为例,B对象中调用了a对象的method1()方法,如果在method1()上开启了事务,此时需要说明method1()需要在method2()中执行怎样的动作,也就是说method2()需要怎样影响到method1()
PS:如果是在同一个方法里进行调用,即B类的method2()调用B类的method1()相当于普通方法调用,也就是AOP不生效的场景之一!

Spring传播行为

spring-tx.jar的org.springframework.transaction package下有一个事务定义信息接口

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;
    ...
}

PROPAGATION前缀的常量即为事务传播行为定义

常量含义
PROPAGATION_REQUIRED当前存在事务就加入事务,当前不存在事务就新建一个事务
PROPAGATION_SUPPORTS当前存在事务就加入事务,当前不存在事务就以非事务方式执行
PROPAGATION_MANDATORY当前存在事务就加入事务,当前不存在事务就抛出异常
PROPAGATION_REQUIRES_NEW不管当前是否存在事务,都开启一个新的事务
PROPAGATION_NOT_SUPPORTED以非事务方式执行,当前存在事务就把事务挂起
PROPAGATION_NEVER以非事务方式执行,当前存在事务就抛出异常
PROPAGATION_NESTED当前存在事务就以嵌套事务方式执行,当前不存在事务就新建一个事务

典型案例

1.对事务进行异常捕获操作

Service1

@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
    jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
            "张三", 20, new Date());
}

Service2

@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
    jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
                "李四", 20, new Date());
    throw new RuntimeException();
}

Service3

@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
    try {
        service2.addRequired();
    } catch (Exception e) {
        System.out.println(e);
    }
    service1.addRequired();
}

Service3中分别调用了service1和service2的事务方法,12方法都因为设置了REQUIRED传播属性,所以会加入到Service3的事务当中,service3中对service2的操作进行了异常捕获操作,内层service2的事务发生了异常进行事务回滚后,spring会把当前事务标记为“rollback-only”,虽然进行了try-catch捕获可以继续向下执行到service1的方法调用,执行完毕后外部事务也处理完毕。当要提交时,spring发现事务已经被标记为“rollback-only”了,但是方法却正常执行完毕,这时spring就会抛出UnexpectedRollbackException

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

2.嵌套事务的异常捕获

Service1

@Transactional(propagation = Propagation.NESTED)
public void addRequired() {
    jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
            "张三", 20, new Date());
}

Service2

@Transactional(propagation = Propagation.NESTED)
public void addRequired() {
    jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
                "李四", 20, new Date());
    throw new RuntimeException();
}

Service3

@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
    try {
        service2.addRequired();
    } catch (Exception e) {
        System.out.println(e);
    }
    service1.addRequired();
}

这里Serivce1和Service2事务方法都被NESTED嵌套事务传播行为修饰,所以二者都会在Service3事务里开启嵌套子事务执行,虽然service2方法抛出了异常,但因为外部事务进行了捕获操作,嵌套的子事务抛出的异常没有逐级向上抛出,而是由外部事务捕获打印,service1的方法得以成功执行。而如果不对其try-catch捕获,或者在catch里接着抛出,那么就相当于该外部事务会因为异常执行回滚操作,导致都不会执行成功。

3.新建事务的异常处理

Service1

@Transactional(propagation = Propagation.NESTED)
public void addRequired() {
    jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
            "张三", 20, new Date());
}

Service2

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequired() {
    jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
                "李四", 20, new Date());
    throw new RuntimeException();
}

Service3

@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
    try {
        service2.addRequired();
    } catch (Exception e) {
        System.out.println(e);
    }
    service1.addRequired();
}

这里的Service2由NESTED改成了REQUIRES_NEW,和案例2其实是一个原理,区别在于Service2这里是新开启了一个事务,即便是抛出了异常同样被外部事务所捕获,导致Service1可以正常执行,一旦异常抛到外部事务里,同样都不会执行。
PS:思考一下,如果把Service1的NESTED也替换成REQUIRES_NEW,那Service1方法会执行成功吗?
答案是:只要对service2进行异常捕获,就可以使service1的方法得以成功执行,即便是不对service2进行异常捕获,只要把service1方法执行放到service2之前新开启一个事务或者嵌套事务都可以执行。只要是try-catch捕获的方法开启的事务和外部事务不是同一个,那前边所说的那个因为“rollback-only”状态而产生的异常也就不会被抛出。被try-catch捕获的方法也就相当于是正常执行状态,只是其事务被回滚而已,不会影响到其他新开启事务的正常执行