zoukankan      html  css  js  c++  java
  • Spring 自调用事务失效,你是怎么解决的?

    前言

    相信大家都遇到一种事务失效场景,那就是 Spring 自调用,就是在 Service 方法内,调用另一个加 @Transactional 注解的方法,发现事务失效,这时候你是怎么解决的呢?

    事情回顾

    那是一个我忘了天气咋样的下午,突然蹦出一个小红点,嗯~ 挺着急的小红点。

    3E8q0W-3Cg65u

    原来是事务失效了!

    莫慌!莫慌!

    kRoqUW-MKbcBj

    KVdtlZ-F3jW3p

    T4poZK-qyiLEV

    最后小伙伴选择了抽走,是我的工具类不香了么?

    zVf6LC-tLzseS

    当然故事的结果是完美的,问题解决了。

    v3W8tg-VJEgZS

    事务

    在开发中涉及到同时操作多个表的时候,要保证两个操作要么一起成功,要么一起失败,这时候就需要用到事务。

    现在一般使用的都是基于 @Transactional 注解的声明式事务

    而事务使用过程中有以下几个注意事项:

    1. 事务只能应用到 public 方法上才会有效;
    2. 事务需要从外部调用,Spring 自调用会失效;
    3. 建议事务注解 @Transactional 一般添加在实现类上。

    当然这几句话不是说我的,人家官方文档可是明确说明的!

    IlNXVn-uemNYF

    这里可是说明了应仅将 @Transactional 注解应用于具有公开可见性的方法。如果对受 protected, private o或 package-visible 修饰的方法使用,则不会引发任何错误,但是被注解的方法不会显示已配置的事务设置。

    说白了,就是你用了,不会报错,但是不生效!

    M5XTck-YnrPiu

    至于建议加在实现类上,这个只是建议,不过如果加在接口类或接口方法上时,只有配置基于接口的代理才会生效。所以这块还是老老实实的加在实现类或实现类方法上吧。

    P28Ciu-4YvetS

    因为代理模式只拦截通过代理传入的外部方法调用,所以自调用事务是不生效的。

    官方的解释还是比较简单明了的,虽然我看不懂,但是不影响我截图。

    那我还是再截一个吧……

    sBkeIz-80K1UE

    实际使用

    但是在开发中,小伙伴们往往会遇到这种情况!

    g1BG6s-BgQfOw

    本来自己写的代码就一坨坨的又臭又长,里面有各种验签、验参、查询、验证等等,就想着来个事务,让事务包裹的范围最小,仅仅在同时更新的时候加上事务吧!

    38SYyX-gEXV58

    这么写,咦~ IDEA 报错了,好像不能 private 修饰,那我改成 public

    很显然事务是不生效的。

    把更新的代码放到又臭又长的代码里面,让它变得更臭更长,然后用 @Transactional 注解一加。完美解决!

    9r8ioC-W6kSbv

    请放过那坨代码吧!来看看下面的办法。

    解决方案 1

    08fPAy-ee1X6A

    那我改成外部调用不就行了么?

    再声明一个 Service,把更新表的逻辑放过去。

    我一般就喜欢使用这个办法。

    解决方案 2

    使用编程式事务,前面说了,使用声明式事务时,又这又那,我换一种总可以吧!

    yuJKad-LGJuVt

    你看,我还把方法改成 private 修饰了,事务也生效。完美解决!

    8orQs4-tgtQts

    其实这个方法也很不错哦!

    解决方案 3

    又想用注解,又想自调用怎么办?

    MBSeHo-E5RDuC

    不过... 麻烦一点还是可以的。

    咱们可以参考编程式事务的方式,不就是不让自调用么,我调外部方法,然后外部方法再给我调回来不就可以了。

    @Component
    public class TransactionalComponent {
    
        public interface Cell {
    
            void run() throws Exception;
        }
    
        @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
        public void required(Cell cell) throws Exception {
    
            cell.run();
        }
    }
    

    这样的话不就可以通过 TransactionalComponent 调用了么,并且还可以使用 lambda 表达式。

    y5bnw5-TMtjKN

    当然基于这个版本也可以做一个迭代,就是使用静态方法调用,不用每次都用 @Autowired 注入一次。

    public class TransactionalUtils {
        
        private static volatile TransactionalComponent transactionalComponent;
    
        private static synchronized TransactionalComponent getTransactionalComponent() {
            if (transactionalComponent == null) {
                // 从容器中获取 transactionalComponent
                transactionalComponent = ApplicationContextUtils.getBean(TransactionalComponent.class);
            }
            return transactionalComponent;
        }
    
        public static void required(TransactionalComponent.Cell cell) throws Exception {
            getTransactionalComponent().required(cell);
        }
    
    }
    

    awzH4i-JjJNp4

    这样通过工具类 TransactionalUtils 便可以直接调用静态方法的方式执行事务操作。

    总结

    结束语

    本文主要介绍为什么会遇到事务失效,以及事务失效的避免方式,同时提供了三种方式来解决自调用事务失效的问题。不足之处,欢迎指正。

    相关资料

    1. Spring 文档:https://docs.spring.io/spring-framework/docs/5.3.0/reference/html/data-access.html#transaction-declarative-annotations
  • 相关阅读:
    《大话数据结构》第1章 数据结构绪论 1.2 你数据结构怎么学的?
    伍迷七八月新浪微博集锦
    《大话数据结构》第9章 排序 9.7 堆排序(下)
    《大话数据结构》第3章 线性表 3.8.2 单链表的删除
    《大话数据结构》第9章 排序 9.5 直接插入排序
    《大话数据结构》第9章 排序 9.8 归并排序(上)
    《大话数据结构》第2章 算法基础 2.9 算法的时间复杂度
    《大话数据结构》第1章 数据结构绪论 1.1 开场白
    《大话数据结构》第9章 排序 9.1 开场白
    [AWS] Assign a public IP address to an EC2 instance after launched
  • 原文地址:https://www.cnblogs.com/liuzhihang/p/spring-self-call.html
Copyright © 2011-2022 走看看