• Spring事务和事务传播机制


    1.为什么需要事务?

    事务定义:将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败。
    比如转账分为两个操作:

    第一步操作:A 账户 -100 元。
    第二步操作:B 账户 +100 元。

    如果没有事务,第一步执行成功了,第二步执行失败了,那么 A 账户平白无故的 100 元就“人间蒸发”了。而如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。

    2.Spring 中事务的实现

    Spring 中的事务操作分为两类:

    1. 手动操作事务(手动写代码操作事务)。
    2. 声明式自动提交事务(利用注解自动开启和提交事务)。
      在开始讲解它们之前,咱们先来回顾事务在 MySQL 中是如何使用的?

    2.1 MySQL 中的事务使用

    事务在 MySQL 有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:

    -- 开启事务
    start transaction;
    -- 业务执行
    
    -- 提交事务
    commit;
    
    -- 回滚事务
    rollback;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.2 Spring 手动操作事务

    Spring 手动操作事务和上面 MySQL 操作事务类似,它也是有 3 个重要操作步骤:

    开启事务(获取事务)。
    提交事务。/回滚事务。(2,3步是或许关系)

    在这里插入图片描述

    package com.example.demo.conyroller;
    
    import com.example.demo.model.UserInfo;
    import com.example.demo.service.UserService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/user")
    @Slf4j
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        //事务管理器
        @Autowired
        private DataSourceTransactionManager transactionManager;
    
        //事务属性对象
        @Autowired
        private TransactionDefinition transactionDefinition;
    
        @RequestMapping("/add")
        public int add(UserInfo userInfo){
            //获取事务(开启事务)
            TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
            int result= userService.add(userInfo);
            log.info("添加结果:"+result);
            //回滚事务
    //        transactionManager.rollback(transactionStatus);
            transactionManager.commit(transactionStatus);//提交事务
            return result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。

    2.3 Spring 声明式事务(自动事务)

    声明式事务的实现很简单,只需要在需要的方法上添加 @Transactional 注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务,具体实现代码如下:

        //开启声明式事务(进入方法自动开启事务,方法正常执行完自动提交事务,
        // 如果发生了未处理的异常会自动回滚事务)
        @Transactional
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
            int result= userService.add(userInfo);
            log.info("添加功能2执行结果:"+result);
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    发生异常:

        //开启声明式事务(进入方法自动开启事务,方法正常执行完自动提交事务,
        // 如果发生了未处理的异常会自动回滚事务)
        @Transactional
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
            int result= userService.add(userInfo);
            log.info("添加功能2执行结果:"+result);
            int num=10/0;//声明式事务执行到此后,因为报错所以会回滚事务
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.3.1 @Transactional 作用范围

    @Transactional 可以用来修饰方法或类:
    1.修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法。
    2. 修饰类时:表明该注解对该类中所有的 public 方法都生效。

    2.3.2 @Transactional 参数说明

    在这里插入图片描述

    2.3.3 注意事项

    @Transactional 在异常被捕获的情况下,不会进行事务自动回滚。

    原因:Spring认为代码加了try catch,那么你就有责任和能力来处理这个异常,所以Spring不会替你回滚事务,而是顺其自然的提交事务。

    验证以下代码是否会发生事务回滚:

        @Transactional
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
            int result=0;
            try{
                result= userService.add(userInfo);
                log.info("添加功能2执行结果:"+result);
                int num=10/0;//声明式事务执行到此后,因为报错所以会回滚事务
            }catch(Exception e){
            }
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    事务不会自动回滚解决方案

    解决方案1:对于捕获的异常,事务是会自动回滚的,因此解决方案1就是可以将异常重新抛出

        @Transactional
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
            int result=0;
            try{
                result= userService.add(userInfo);
                log.info("添加功能2执行结果:"+result);
                int num=10/0;//声明式事务执行到此后,因为报错所以会回滚事务
            }catch(Exception e){
                throw e;
            }
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    解决方案2:在代码中得到当前的事务,使用代码手动的回滚事务

        @Transactional
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
            int result=0;
            try{
                result= userService.add(userInfo);
                log.info("添加功能2执行结果:"+result);
                int num=10/0;//声明式事务执行到此后,因为报错所以会回滚事务
            }catch(Exception e){
    //            throw e;
                log.info("程序执行出现异常"+e.getMessage());
                //手动回滚事务
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.3.4 @Transactional工作原理

    @Transactional 是基于 AOP 实现的,AOP 又 是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采 JDK 的动态代理,如果目标对象没有实现了接口,会使 CGLIB 动态代理。
    @Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。

    @Transactional 实现思路预览:
    在这里插入图片描述

    @Transactional 具体执行细节如下图所示:

    在这里插入图片描述

    3.事务隔离级别

    3.1 事务特性回顾

    事务有4大特性(ACID),原子性、持久性、一致性和隔离性,具体概念如下:

    原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发 错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

    一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以 发性地完成预定的工作。(转账操作,张三余额50元,李四余额30元,不管张三李四如何转账,他俩的总余额都是80元)

    持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

    隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

    上面 4 个属性,可以简称为ACID。

    原子性(Atomicity,或称不可分割性)
    一致性(Consistency)
    隔离性(Isolation,又称独立性)
    持久性(Durability)。

    而这 4 种特性中,只有隔离性(隔离级别)是可以设置

    为什么要设置事务的隔离级别?

    设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的。

    什么是可控呢?

    比如近几年比较严重的新冠病毒,我们会把直接接触到确证病例的人员隔离到酒店,而把间接接触者(和直接接触着但未确诊的人)隔离在自己的家中,也就是针对不同的人群,采取不同的隔离级别,这种隔离方式就和事务的隔离级别类似,都是采取某种行动让某个事件变的“更可控”。而事务的隔离级别就是为了防止,其他的事务影响当前事务执行的一种策略。

    3.2 Spring 中设置事务隔离级别

    3.2.1 MySQL 事务隔离级别有 4 种

    1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读

    2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。

    3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读(Phantom Read)。

    4. SERIALIZABLE:序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。

    在这里插入图片描述

    ● 脏读:一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。

    ● 不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。(对同一个数据的一个修改)

    ● 幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。(增加和删除)

    3.2.2 Spring 事务隔离级别有 5 种

    1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主

    2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。

    3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。

    4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。

    5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。

    从上述介绍可以看出,相比于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了一个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。

    Spring事务隔离级别设置:

    Spring 中事务隔离级别只需要设置 @Transactional 里的 isolation 属性即可,具体实现代码如下:

        @Transactional(isolation = Isolation.DEFAULT)
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
    }
    
    • 1
    • 2
    • 3
    • 4

    4.Spring 事务传播机制

    4.1 事务传播机制是什么?

    Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。

    4.2 为什么需要事务传播机制?

    事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性的(稳定性的)

    例子:像新冠病毒一样,它有不同的隔离方式(酒店隔离还是居家隔离),是为了保证疫情可控,然而在每个人的隔离过程中,会有很多个执行的环节,比如酒店隔离,需要负责人员运送、物品运送、消杀原生活区域、定时核算检查和定时送餐等很多环节,而事务传播机制就是保证一个事务在传递过程中是可靠性的,回到本身案例中就是保证每个人在隔离的过程中可控的。

    事务隔离级别解决的是多个事务同时调用一个数据库的问题,如下图所示:

    在这里插入图片描述
    而事务传播机制解决的是一个事务在多个节点(方法)中传递的问题,如下图所示:

    在这里插入图片描述

    4.3 事务传播机制有哪些?

    Spring 事务传播机制包含以下 7 种:

    1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一 个新的事务。
    2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    4. Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
    5. Propagation.NOT_SUPPORTED:以非事务方式运 ,如果当前存在事务,则把当前事务挂起。
    6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
    7. Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。

      以上 7 种传播 为,可以根据是否 持当前事务分为以下 3 类:
      在这里插入图片描述
      以情侣关系为例来理解以上分类:

    在这里插入图片描述

    4.4 Spring 事务传播机制使用和各种场景演示

    事务传播机制的设置:

     @Transactional(propagation = Propagation.REQUIRED)
        @RequestMapping("/add2")
        public int add2(UserInfo userInfo){
        }
    
    • 1
    • 2
    • 3
    • 4

    嵌套事务(NESTED)和加入事务(REQUIRED )的区别:

    • 整个事务如果全部执行成功,二者的结果是一样的。
    • 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果。
  • 相关阅读:
    全国大学生智能汽车大赛(三):上下位机通信协议及代码
    Window 平台开发基础(一)SDK
    听GPT 讲Istio源代码--cni
    【异常报错】must call Vue.use(Vuex)
    2022年全球市场石膏发泡剂总体规模、主要生产商、主要地区、产品和应用细分研究报告
    【vue3+ts后台管理】角色列表
    clickhouse使用入门
    生命之水 I 永恒君主雪莉白兰地大师班
    40.讲初识动态规划:如何巧妙解决“双十一”购物时的凑单问题
    [附源码]java毕业设计“云味坊”购物网站
  • 原文地址:https://blog.csdn.net/mzpqq/article/details/126133157