日日行,不怕千万里

什么是 Spring 的事务传播机制?项目中遇到长事务怎么处理?相信这是大多数人面试时会被问到的高频问题,那今天就来聊聊这个问题。

事务

什么是事务?

事务这个词大家都不会陌生,甚至 ACID 背的滚瓜烂熟,被问到这个,心里一喜,心想这不是手到擒来吗?直接撞咱枪口上了。

接下来你了你的表演:

  • 事务的特性

    • 原子性:一个事务中的所有操作,要么全部成功,要么全部失败,没有中间状态。

    • 一致性:事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态,这是事务追求的最终状态,可以说其他的三个特性都是为了保证事务的一致性。

    • 隔离性:事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰,严格的隔离性对应了串行化执行,但是实际操作中很少会用到串行化的隔离级别,一般来说可以分两个方面来保证隔离性。

      • A 事务写对 B 事务写的影响:通过锁来保证
      • A 事务写对 B 事务读的影响:通过 MVCC 来保证
    • 持久性:事务一旦提交,对数据库的影响应该是永久性的。

那你平时在项目中是如何使用到事务的?

哎呀,那这不又是正中下怀吗?

首先,Spring 对事务的支持建立在你所使用的数据库,如果你使用的是 mysql 数据库,恰好又是使用的 innodb 引擎,那么恭喜你,是支持事务的。那 Spring 对事物的支持一般有两种方式:

  • 编程式事务管理:通过 TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用,这不是本文的重点,就不在这里赘述。
    public class Test1 {
        @Autowired
        private TransactionTemplate transactionTemplate;
        public void testProgrammingTransaction() {

            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    try {
                        // ....  do something
                    } catch (Exception e){
                        //rollback
                        transactionStatus.setRollbackOnly();
                    }

                }
            });
        }
    }
    
    
    ```
    
 
*   **声明式事务管理**:使用场景最多,也是最推荐使用的方式,直接加上 @Transactional 注解即可。
 
那你项目中遇到长事务是如何处理的呢,如果有一百个操作,怎么处理这种事务呢?
 
事务嵌套是如何处理的呢?再比如 A 事务中嵌套了 B 事务,B 事务成功开启了吗?
 
完了完了,你开始支支吾吾,面试官看你回答不上来,问了几个无关紧要的问题,然后心不在焉的问你有什么要问他的,你知道,凉了,要回去等通知了...
 
## Spring 事务传播机制
 
你回去仔细研究了一下发现,事务传播机制一共有七种:
 
`PROPAGATION_REQUIRED`:Spring 的默认传播级别 **,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行**
 
`PROPAGATION_SUPPORTS`:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
 
`PROPAGATION_MANDATORY`:该传播级别要求上下文中必须存在事务,否则抛出异常。
 
`PROPAGATION_REQUIRES_NEW`:每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。
 
`PROPAGATION_NOT_SUPPORTED`:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。
 
`PROPAGATION_NEVER`:该传播级别要求上下文中不能存在事务,否则抛出异常。
 
`PROPAGATION_NESTED`:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务
 
哦豁,已看完这个,已经更晕了,于是你下定决心一定要搞懂,不然下次面试又得嗝屁。你去翻了翻网上大神的文章,又问了问身边的同事大佬,大佬告诉你,这七个中,你只需要记住其中两个就能应付大多数的问题,这里的大多数是大于等于 90%
 
*   **PROPAGATION_REQUIRED**:Spring 默认传播级别当然不用多说,那如何理解上下文中如果存在事务,就加入当前事务呢,如果不存在就新建事务执行呢?
    
    *   如果 A 事务中如果嵌套了 B 事务,B 事务也不会是单独的,也会加入到 A 事务中。
    *   如果 A 事务失败,回滚。
    *   如果 A 事务中,B 事务开始前的前置操作执行成功,事务不会提交,继续执行 B 事务。
    *   如果 B 事务失败,**因为 B 事务是加入了当前事务(上下文中的事务,A 事务),连同 A 事务一起回滚**
    
    在下面代码中,a 不可以正常插入数据库,会造成回滚。原因在于使用的 Spring 默认的事务传播机制,Test1 的事务会加入到 Test 的事务中。
    
```java
    public class Test {
        @Autowired
        private static Test1 test1;
        @Autowired
        private static AMapper aMapper;
        @Transactional
        public void testTransaction(A a) throws Exception {
            aMapper.insert(a);
            try {
                test1.required();
            }catch (Exception e){
                System.out.println(e);
            }
        }
    }
    
    
   public class Test1 {
       @Transactional
       public void required() {
           throw new NullPointerException("throw a test exception");
       }
   }
   
   
  • PROPAGATION_REQUIRES_NEW:每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务,这句话应该比较好理解了,可以理解为事务是单独的,嵌套的事务不会影响上下文中的事务。

    • 如果 A 事务中嵌套了 B 事务,B 事务会是单独执行的,此过程中 A 事务会暂时挂起
    • 如果 B 事务执行成功,B 事务提交,A 事务恢复
    • 如果 B 事务执行失败,B 事务回滚,A 事务恢复
    • B 对于 A 来说是单独的,B 事务的失败不会造成 A 事务的失败,当然是在有 try-catch 的情况下。
    • 如果没有 try-catch 或者说 A 事务直接执行失败了,不用想,数据是肯定会插入失败的。

    就像下面的代码一样,有 try-catch,a 可以插入成功,如果没有 try-catch,a 一定会插入失败。

    public class Test {
        @Autowired
        private static Test1 test1;
        @Autowired
        private static AMapper aMapper;
        @Transactional
        public void testTransaction(A a) throws Exception {
            aMapper.insert(a);
            try {
                test1.required();
            }catch (Exception e){
                System.out.println(e);
            }
        }
    }
    
    
    ```
    
```java
    public class Test1 {
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void required() {
            throw new NullPointerException("throw a test exception");
        }
    }
    
    

其他情况

那么如果遇到其他情况,乖乖的来看你需要的是哪种 Spring 事务传播机制吧。

另:@Transactional 默认情况下,只回滚 RuntimeException 哦。

后记

你不可能真想把这玩意全背下来吧,退一万步讲,就算你全背下来了,面试官会不知道你是背的?

面试官内心 OS: 这能全知道?肯定这小子是个卷王,八股文背的这么溜,要是招进来,还不得把我也卷出去?

这些文章我有空会给大家做一个整理,到时候给大家放在一个地方,如果有疑惑或者问题,欢迎指出或者私信我哦。

同时也希望大家可以关注我的公众号【类似程序员】,会定期与大家分享我的一些想法与学习感悟,谢谢各位!

[[TagFolder/#简历面试/框架篇讲义#4. Spring 事务失效]]