What?天天用Spring你竟然不知道事务的传播性?

Posted 程序员大咖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了What?天天用Spring你竟然不知道事务的传播性?相关的知识,希望对你有一定的参考价值。

????????关注后回复 “进群” ,拉你进程序员交流群????????

作者丨故里

来源丨故里学Java

在我们日常的开发中Spring是必备的技能,在面试的时候,这一块的知识也会着重地问,虽然每天都在使用,但是稍不注意就会出问题,今天这篇文章我们来详细的聊聊Spring的事务传播性,助力金三银四面试季。

什么是Spring事务传播性?Spring事务传播性是当多个包含事务的方法嵌套调用的时候,处理事务的规则。例如:两个事务方法A、B,当方法A调用方法B的时候,方法B是合并到方法A的事务中还是开启一个新的事务。如果是合并到方法A的事务中,那么当方法B回滚之后,方法A会不会回滚等等。Spring有几种处理这种嵌套事务的方式?通过源码我们发现有7种,定义在Propagation这个枚举类中,接下来我们讲详细说一下每一种传播行为都可以帮助我们处理什么样的问题。

1、Propagation.REQUIRED

这种传播行为是Spring默认的,当我们使用@Transactional注解且不指定传播行为的时候就是使用这个,它指的是外层的调用方法如果开启了事务,那么当前方法就合并到外层的事务中执行,如果外层调用方法没有开启事务,就开启一个事务执行当前方法。

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //调用服务B的methodB方法
        serviceB.methodB();
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

我们的实例代码,服务A的methodA方法调用了服务B的methodB方法,并且我们给methodA通过注解@Transactional加了一个事务,并定义了传播性为REQUIRED。

methodA本身开启了事务,methodB也开启了事务,且事务的传播性为REQUIRED,所以当methodA调用methodB的时候,methodB会合并到methodA开启的事务中执行。这个时候两个方法是在一个事务中执行的,当两个方法都执行成功后提交事务。

这个地方很多人就会犯迷糊啦,如果methodB在执行过程中抛出了异常,那么methodB会回滚,那么methodA执行的操作会回滚吗?这里其实只要记住一点,这两个操作是在同一个事务中,事务是原子性操作的,所以methodA也会回滚。

面试的时候还会进一步挖坑!如果methodA中使用try-catch捕获了异常,那么methodA执行的操作还会回滚吗?

这里还是要牢记事务本身具有原子性,所以无论有没有catch异常,都会回滚的。

2、Propagation.SUPPORTED

这个传播行为是说,如果当前方法的调用方开启了事务,那么当前方法就合并到外层事务中执行,如果外层事务没有开启事务,那么当前方法也不会创建事务,就不开启事务执行。

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //调用服务B的methodB方法
        serviceB.methodB();
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTED)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

我们看到,methodB开启了事务,传播性为SUPPORTED,methodA没有开启事务,那么methodA执行的时候不会开启事务,在调用methodB的时候,由于methodB开启了事务,但传播性为SUPPORTED,所以methodB也不会开启事务,以非事务的方式运行。

如果methodA开启了事务,那么methodB会合并到methodA的事务中执行。

3、Propagation.MANDATORY

这个传播行为是指,传播性为MANDATORY的方法只能被开启事务的方法调用,如果调用方没有开启事务就会抛出异常。

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //调用服务B的methodB方法
        serviceB.methodB();
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

我们的示例中,methodA没有开启事务,调用了开启事务并且传播性为MANDATORY的methodB,这时,执行methodA的业务操作时不开启事务,在调用服务B的methodB方法的时候,就会抛出异常:

IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'")

4、Propagation.REQUIRES_NEW

这个传播行为是指,每次都会开启一个新的事务来执行当前方法。比如调用放methodA开启了事务,在methodA中调用开启了事务且传播性为REQUIRES_NEW的方法methodB,那么在methodA会开启一个事务执行自己的业务代码,在调用methodB的时候的时候会先挂起methodA的事务,然后开启一个新的事务执行methodB,在methodB的事务提交后,会恢复methodA的事务继续执行。

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //调用服务B的methodB方法
        try{
            serviceB.methodB();
        } catch (Exception e){
            
        }
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

我们的实例代码中,methodA开启了事务,传播性为REQUIRED,所以在执行的时候,methodA会开启一个事务A,然后执行methodA的业务,在调用methodB的时候,由于methodB开启了事务,且事务传播性为REQUIRES_NEW,,所以这个时候就先挂起事务A,重新开启一个事务B来执行methodB,在methodB执行完提交事务后,会恢复事务A的执行,最后再提交事务A。

这个地方面试的时候可能会问到,methodB在执行的过程中出现了异常整个过程会发生什么变化?

我们根据上边的调用图分析,在methodB执行过程中抛出异常,事务B会回滚,如果methodA中调用methodB的时候catch住了异常,并没有向外排除,那么methodA不会回滚,如果methodA中没有处理异常,那么methodA也会回滚。

5、Propagation.NOT_SUPPORTED

这个传播性就是不支持事务,如果调用方开启了事务,那么在执行的时候会先挂起调用方的事务,以非事务的方式执行当前的业务,在执行完之后,再恢复调用方的事务继续执行。

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //调用服务B的methodB方法
        serviceB.methodB();
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

在我们的实例代码中,methodA开启了事务,传播性为REQUIRED,methodB的传播性为NOT_SUPPORTED,在执行的过程中,methodA会开启一个事务A,在调用methodB的时候,会先挂起methodA的事务A,然后以非事务的方式执行methodB的业务,在methodB执行完之后,恢复事务A,最后提交事务A。整个过程如下图:

6、Propagation.NEVER

这个传播性和前一种传播性都是不支持事务,但是不同的是这种传播性是调用方如果开启了事务,那么在执行当前方法的时候就会抛出异常。下边还是通过一个示例来看:

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //调用服务B的methodB方法
        serviceB.methodB();
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

示例中我们看到,methodA开启了事务,传播性为REQUIRED,methodB的传播性为NEVER,那么在methodA调用methodB的时候,就会抛出如下异常:

IllegalTransactionStateException(
                    "Existing transaction found for transaction marked with propagation 'never'")

7、Propagation.NESTED

这个传播性和REQUIRED很相似,都是当调用方没有开启事务时,就开启一个新的事务,如果调用方开启了事务就合并到调用方的事务中执行,不同的地方就是NESTED这种传播行为可以保存状态点,当事务回滚的时候,可以回滚到某一个地方,从而避免了嵌套事务全部回滚的情况。

//服务A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //methodA 的业务操作
        System.out.println("methodA执行业务");
        //
        try{
            serviceB.methodB();
        }catch(Exception e) {
            
        }
        //methodA在methodB之后的业务操作...
        update();
    }
}

//服务B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    public void methodB() {
        System.out.println("methodB执行业务");
    }
}

在这个示例中,我们可以看到,在methodA执行的时候,如果没有开启事务,会先开启一个事务,然后执行methodA的业务操作;在实行调用服务B的methodB的时候,由于其传播行为NESTED,所以会创建一个savepoint,用于标记methodA执行的业务操作。

然后methodB的业务操作是在methodA的事务中进行的,当methodB抛出异常时,methodB中的业务操作会回滚掉,methodA执行的业务操作并不会回滚,因为在执行methodB之前创建了savepoint,methodB只会回滚到这个savepoint点之前。

这个地方注意的是,methodB回滚以后,对于methodA在methodB之后的业务操作是会被提交的,并不受methodB回滚的影响。

最后

我们常用的事务传播行为其实只有两种,分别是REQUIRED和REQUIRED_NEW。其余五种传播行为只需要了解即可,可以在面试的时候展示一下知识面。

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击????卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

以上是关于What?天天用Spring你竟然不知道事务的传播性?的主要内容,如果未能解决你的问题,请参考以下文章

面试官:知道Spring事务传播行为吗?多个方法之间调用事务如何传播?还好我看过

Spring支持的常用数据库事务传播属性和隔离级别

面试官:你天天用 Lombok,说说它什么原理?我竟然答不上来…

无语!你竟然连CompletableFuture都不知道,还天天说在jdk8原地踏步~

面试官:你天天用 Lombok,说说它什么原理?我竟然答不上来…

面试官:你天天用 Lombok,说说它什么原理?我竟然答不上来…