当前位置: 首页 > 科技观察

什么?天天用Spring,事务的传播你都不知道?

时间:2023-03-13 02:58:13 科技观察

本文转载自微信公众号《在家乡学Java》,作者家乡。转载本文请联系家乡学Java公众号。在我们日常的开发中,Spring是必备的技能。面试的时候也会重点问这块知识。虽然每天都在用,但稍不注意就会出问题。今天我们将在本文中详细解释它。说说Spring的商务沟通,助力金银四面试季。什么是Spring事务传播?Spring事务传播是嵌套调用多个包含事务的方法时处理事务的规则。例如:两个事务方法A和B,当方法A调用方法B时,方法B是合并到方法A的事务中还是开始一个新的事务。如果合并到方法A的事务中,那么方法B回滚时,方法A也会回滚,以此类推。Spring有多少种方式来处理这种嵌套事务呢?通过源码我们发现一共有7种,定义在枚举类Propagation中。接下来,我们来谈谈每种传播行为可以帮助我们处理什么。问题。1、Propagation.REQUIRED的传播行为是Spring默认的。这在我们使用@Transactional注解并且没有指定传播行为时使用。它指的是外部调用方法。如果启用了事务,则当前方法会合并到外层事务中执行,如果外层调用方法没有开启事务,则启动一个事务来执行当前方法。//ServiceA@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)publicvoidmethodA(){//methodA的业务运行System.out.println("methodA执行业务");//调用服务B的methodB方法serviceB.methodB();}}//服务B@ServicepublicclassServiceB{@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)publicvoidmethodB(){System.out.println("methodB执行业务");}}在我们的示例代码中,服务A的methodA方法调用了服务B的methodB方法,我们通过注解@Transactional为methodA添加了一个事务,定义传播为REQUIRED。methodA本身启动了一个事务,methodB也启动了一个事务,事务的传播是REQUIRED,所以当methodA调用methodB时,methodB会被合并到methodA启动的事务中执行。此时,这两个方法是在一个事务中执行的,两个方法都执行成功,事务就被提交。很多人会在这个地方感到困惑。如果methodB在执行过程中抛出异常,那么methodB会回滚,methodA执行的操作会不会被回滚?其实只要记住这两个操作是在同一个事务中的,事务是一个原子操作,所以methodA也会回滚。在采访中,我们将进一步挖掘!如果methodA使用try-catch捕获异常,methodA执行的操作还会回滚吗?还是要记住事务本身是原子的,所以不管有没有catch异常,都会返回Getout。2、Propagation.SUPPORTED的传播行为是指如果当前方法的调用者启动了一个事务,则当前方法会被合并到外层事务中执行。如果外层事务没有启动事务,那么当前方法就不会创建事务。不启动事务执行。//服务A@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;publicvoidmethodA(){//methodA的业务运行System.out.println("methodA执行业务");//调用服务B的methodB方法serviceB.methodB();}}//ServiceB@ServicepublicclassServiceB{@Transactional(rollbackFor=Exception.class,propagation=Propagation.SUPPORTED)publicvoidmethodB(){System.out.println("methodB执行业务");}}我们看到methodB开启事务,传播If属性为SUPPORTED且methodA不开启事务,则methodA执行时不会开启事务。在调用methodB时,由于methodB开启了事务,但是propagating属性是SUPPORTED,methodB也不会开启事务,以非事务方式运行。如果methodA启动了一个事务,那么methodB会被合并到methodA的事务中执行。3、Propagation.MANDATORY的传播行为是指传播为MANDATORY的方法只能被启动事务的方法调用。如果调用者没有启动事务,将抛出异常。//服务A@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;publicvoidmethodA(){//methodA的业务运行System.out.println("methodA执行业务");//调用服务B的methodB方法serviceB.methodB();}}//ServiceB@ServicepublicclassServiceB{@Transactional(rollbackFor=Exception.class,propagation=Propagation.MANDATORY)publicvoidmethodB(){System.out.println("methodB执行业务");}}在我们的例子中,methodA没有开启事务,启动事务并具有MANDATORY传播的methodB被调用。此时执行methodA的业务操作时并没有启动事务。当调用服务B的methodB方法时,会抛出异常:IllegalTransactionStateException("Noexistingtransactionfoundfortransactionmarkedwithpropagation'mandatory'")4.Propagation.REQUIRES_NEW的传播行为是指每次都会开启一个新事务来执行当前方法。比如调用methodA启动一个事务,在methodA中调用methodB已经启动了一个事务,传播属性为REQUIRES_NEW,那么methodA中会启动一个事务执行自己的业务代码,当methodB执行时,methodA会先挂起被称为事务,然后开始一个新的事务来执行methodB。methodB的事务提交后,methodA的事务会恢复继续执行。//ServiceA@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)publicvoidmethodA(){//methodA的业务运行System.out.println("methodA执行业务");//调用服务B的methodB方法{System.out.println("methodBexecutesbusiness");}}在我们的示例代码中,methodA启动了一个事务,传播是REQUIRED,所以在执行的时候,methodA会启动一个事务A,然后执行methodA的业务,在调用methodB的时候,因为methodB开启了一个事务,事务传播是REQUIRES_NEW,所以这时候先挂起事务A,重新开启一个事务B执行methodB。methodB执行并提交事务后,事务A会恢复执行,最终提交事务A。这里面试的时候,你可能会问,如果methodB执行过程中出现异常,整个流程会怎样?根据上面调用图的分析,如果methodB在执行过程中抛出异常,事务B就会回滚。如果在methodA中调用methodB时,异常被捕获,没有排除,那么methodA不会回滚。如果异常没有在methodA中处理,那么methodA也会被回滚。5、Propagation.NOT_SUPPORTED的传播不支持事务。如果调用者启动了事务,则调用者的事务在执行时会先被挂起,以非事务的方式执行当前业务。恢复呼叫者的交易继续。//ServiceA@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)publicvoidmethodA(){//methodA的业务运行System.out.println("methodA执行业务");//调用服务B的methodB方法serviceB.methodB();}}//服务B@ServicepublicclassServiceB{@Transactional(rollbackFor=Exception.class,propagation=Propagation.NOT_SUPPORTED)publicvoidmethodB(){System.out.println("methodBexecutebusiness");}}在我们的示例代码中,methodA启动了一个传播属性为REQUIRED的事务,而methodB的传播属性为NOT_SUPPORTED。在执行过程中,methodA会开启一个事务A,在调用methodB时,会先SuspendmethodA的事务A,然后以非事务的方式执行methodB的业务,methodB执行完毕后resume事务A,最后commit事务A。整个流程如下图所示:6.Propagation.NEVER的传播和之前的传播都不支持事务,但不同的是,如果调用者开启事务,则在当前方法执行时执行.抛出异常。下面看一个例子://ServiceA@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)publicvoidmethodA(){//methodA的业务操作System.out.println("methodAexecutesbusiness");//调用服务B的methodB方法serviceB.methodB();}}//服务B@ServicepublicclassServiceB{@Transactional(rollbackFor=Exception.class,propagation=Propagation.NEVER)publicvoidmethodB(){System.out.println("methodBexecutesbusiness");}}在例子中我们可以看到methodA开启了一个事务,propagation是REQUIRED,而methodB的propagation是NEVER,那么当methodA调用methodB时,会出现如下异常被抛出:IllegalTransactionStateException("Existingtransactionfoundfortransactionmarkedwithpropagation'never'")7.Propagation.NESTED的传播与REQUIRED非常相似。当调用者没有开启事务时,开启一个新的事务。如果调用者打开一个事务,它被合并到调用中。不同的是NESTED传播行为可以保存状态点。事务回滚时,可以回滚到某个地方,从而避免了所有嵌套事务的回滚。//ServiceA@ServicepublicclassServiceA{@AutowiredprivateServiceBserviceB;@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)publicvoidmethodA(){//methodA的业务运行System.out.println("methodA执行业务");//try{serviceB.methodB();}catch(Exceptione){}//methodA在methodB之后的业务运行...update();}}//serviceB@ServicepublicclassServiceB{@Transactional(rollbackFor=Exception.class,propagation=Propagation.NESTED)publicvoidmethodB(){System.out.println("methodB执行业务");}}在这个例子中我们可以看到methodA执行时,如果没有开启事务,会先开启一个事务,然后执行methodA的业务操作;当调用服务B的methodB时,由于其传播行为是NESTED的,因此会创建一个保存点来标记methodA执行的业务操作。然后在methodA的事务中进行methodB的业务操作。当methodB抛出异常时,methodB中的业务操作会回滚,methodA执行的业务操作不会回滚,因为保存点是在methodB执行之前创建的,methodB只会回滚到这个保存点之前。这里注意,methodB回滚后,methodB之后methodA的业务操作都会被提交,不会受到methodB回滚的影响。最后,我们常用的事务传播行为其实只有两种,分别是REQUIRED和REQUIRED_NEW。剩下的五个沟通行为只需要了解,在面试的时候就可以展示自己的知识了。