• SpringBoot 全局事务配置


    前言

    传统springboot实现事务只需要在方法上添加@Transactional注解,但是需要在所有的service都加上事务,相对比较麻烦,随着项目的庞大,功能模块会随之增多,所以就需要采用AOP的方式实现全局事务处理。
    全局事务配置通过AOP切面指定方法前缀切入点,从而对指定的方法统一进行事务控制,根据方法名前缀来匹配到具体方法,进行事务配置

    一、什么是事务?

    提到事务,你肯定不陌生,和数据库打交道的时候,我们总会用到事务

    案例

    你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱。转账过程具体到程序里会有一系列的操作,比如查询余额、做加减法、更新余额等,这些操作必
    须保证是一体的,不然等程序查完之后,还没做减法之前,你这 100 块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?这时就要用到 “ 事务 ” 这个概念了。

    概念

    简单来说,事务就是保证数据操作,要么全部成功,要么全部失败。MySQL 虽然支持多引擎系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAMInnoDB 取代的重要原因之一。

    二、事务的特性(ACID)

    • 原子性(Atomicity):在事务操作中,要么全部成功,要么全部失败。
    • 一致性(Consistency):事务改变前后,状态一致。
    • 隔离性(Isolation):两个事物直接互不干扰。
    • 持久性(Durability):持久到硬盘。

    三、隔离性与隔离级别

    为什么需要隔离

    当数据库上有多个事务同时执行时候,就会出现并发问题:

    • 脏读( dirty read):对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段之后,若T2回滚,T1读取的内容就是临时且无效的。
    • 不可重复读( non-repeatable read ):对于两个事务T1,T2,T1读取了一个字段,然后T2更新了该字段之后,T1在读取同一个字段,值就不同了。
    • 幻读( phantom read):对于两个事务T1,T2,T1在A表中读取了一个字段,然后T2又在A表中插入了一些新的数据时,T1再读取该表时,就会发现神不知鬼不觉的多出几行了…

    为了解决这些问题,就有了 “ 隔离级别 ” 的概念。

    隔离级别

    重点: 隔离得越严实,效率就会越低。
    SQL 标准的事务隔离级别包括:

    • 读未提交( read uncommitted ): 一个事务还没提交时,它做的变更就能被别的事务看到(脏读、不可重复读和幻读的问题都会出现)。
    • 读提交( read committed ): 一个事务提交之后,它做的变更才会被其他事务看到(可以避免脏读,但不可重复读和幻读的问题仍然可能出现)。
    • 可重复读( repeatable read ): 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新(update)。(可以避免脏读和不可重复读,但幻读仍然存在)
    • 串行化( serializable ): 顾名思义是对于同一行记录, “ 写 ” 会加 “ 写锁 ” , “ 读 ” 会加 “ 读锁 ” 。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

    其中 “ 读提交 ” “ 可重复读 ” 比较难理解,所以我用一个例子说明这几种隔离级别。假设数据表 T 中只有一列,其中一行的值为 1 ,下面是按照时间顺序执行两个事务的行为。

    mysql> create table T(c int) engine=InnoDB;
    insert into T(c) values(1);
    
    • 1
    • 2

    在这里插入图片描述
    在不同的隔离级别下,事务 A 会有哪些不同的返回结果,也就是图里面 V1 、 V2 、 V3 的返回值分别是什么。

    • 若隔离级别是 “ 读未提交 ” , 则 V1 的值就是 2 。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此, V2 、 V3 也都是 2 。
    • 若隔离级别是 “ 读提交 ” ,则 V1 是 1 , V2 的值是 2 。事务 B 的更新在提交后才能被 A 看到。所以V3 的值也是 2 。
    • 若隔离级别是 “ 可重复读 ” ,则 V1 、 V2 是 1 , V3 是 2 。之所以 V2 还是 1 ,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
    • 若隔离级别是 “ 串行化 ” ,则在事务 B 执行 “ 将 1 改成 2” 的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1 、 V2 值是 1 , V3 的值是 2 。

    好了,概念说完了,下面进入主题,SpringBoot 全局事务配置。

    四、SpringBoot 全局事务配置

    /**
     * @author lanys
     * @author Think
     * @title: SpringTxAspect
     * @projectName material_cloud
     * @description: Spring 全局事务切面配置
     * @date 2022/11/2 12:18
     */
    @Aspect
    @Configuration
    public class SpringTxAspect {
    
        /** 切面,根据自己的项目定义不同的表达式execution **/
        private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.material_cloud.short_link.modules.service.impl.*.*(..))";
    
        @Resource
        private PlatformTransactionManager transactionManager;
    
        /**
         * 增强(事务)的属性的配置
         *
         * @title: txAdvice
         * @author lanys 2022-11-02
         * @Description: 配置
         * @param
         * @return TransactionInterceptor
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Bean
        public TransactionInterceptor txAdvice() {
            NameMatchTransactionAttributeSource txAttributeS = new NameMatchTransactionAttributeSource();
            RuleBasedTransactionAttribute requiredAttr = new RuleBasedTransactionAttribute();
            // PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中
            requiredAttr.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            // 抛出异常后执行切点回滚
            requiredAttr.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
            //
            RuleBasedTransactionAttribute supportsAttr = new RuleBasedTransactionAttribute();
            // PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
            supportsAttr.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
            // 只读事务,不做更新操作
            supportsAttr.setReadOnly(true);
    
            // 注意:方法名称来自类匹配的到方法【save*, “*”表示匹配任意個字符】
            Map txMethod = new HashMap();
            txMethod.put("insert*", requiredAttr);
            txMethod.put("add*", requiredAttr);
            txMethod.put("update*", requiredAttr);
            txMethod.put("modify*", requiredAttr);
            txMethod.put("remove*", requiredAttr);
            txMethod.put("delete*", requiredAttr);
            txMethod.put("bind*", requiredAttr);
            txMethod.put("unbind*", requiredAttr);
            // readOnly = true
            txMethod.put("select*", supportsAttr);
            txMethod.put("get*", supportsAttr);
            txMethod.put("find*", supportsAttr);
            txMethod.put("query*", supportsAttr);
            txMethod.put("read*", supportsAttr);
            txMethod.put("check*", supportsAttr);
            //
            txAttributeS.setNameMap(txMethod);
            TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, txAttributeS);
            return txAdvice;
        }
    
        /**
         * AOP配置定义切面和切点的信息
         *
         * @title: txAdviceAdvisor
         * @author lanys 2022-11-02
         * @Description: AdvisorBean
         * @return Advisor
         */
        @Bean
        public Advisor txAdviceAdvisor() {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
            return new DefaultPointcutAdvisor(pointcut, txAdvice());
        }
    }
    
    • 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
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
  • 相关阅读:
    QT实战之翻金币游戏【详细过程及介绍】
    Java开发中如何配合MySQL实现读写分离?
    opencv dnn模块 示例(17) 目标检测 object_detection 之 yolo v5
    安装国产系统Kylin-Desktop实战
    Hive (六) --------- 查询
    初级CRUD程序员 打工人流程规范
    改进YOLO系列 | YOLOv5/v7 引入 Dynamic Snake Convolution | 动态蛇形卷积
    嵌入式开发:选择嵌入式GUI生成器时要注意什么
    Web应用安全测试-权限缺失
    FFmpeg封装函数avformat_open_input()
  • 原文地址:https://blog.csdn.net/qq_44697754/article/details/127554080