• 面试突击83:什么情况会导致@Transactional事务失效?


    一个程序中不可能没有事务,而 Spring 中,事务的实现方式分为两种:编程式事务和声明式事务,又因为编程式事务实现相对麻烦,而声明式事务实现极其简单,所以在日常项目中,我们都会使用声明式事务 @Transactional 来实现事务。

    @Transactional 使用极其简单,只需要在类上或方法上添加 @Transactional 关键字,就可以实现事务的自动开启、提交或回滚了,它的基础用法如下:

    @Transactional 
    @RequestMapping("/add")
    public int add(UserInfo userInfo) {
        int result = userService.add(userInfo);
        return result;
    }
    

    @Transactional 执行流程

    @Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。

    然而,就是看起来极其简单的 @Transactional,却隐藏着一些“坑”,这些坑就是我们今天要讲的主题:导致 @Transactional 事务失效的常见场景有哪些?

    在开始之前,我们先要明确一个定义,什么叫做“失效”?

    本文中的“失效”指的是“失去(它的)功效”,也就是当 @Transactional 不符合我们预期的结果时,我们就可以说 @Transactional 失效了。

    那 @Transactional 失效的场景有哪些呢?接下来我们一一来看。

    1.非 public 修饰的方法

    当 @Transactional 修饰的方法为非 public 时,事务就失效了,比如以下代码当遇到异常之后,不能自动实现回滚:

    @RequestMapping("/save")
    int save(UserInfo userInfo) {
        // 非空效验
        if (userInfo == null ||
            !StringUtils.hasLength(userInfo.getUsername()) ||
            !StringUtils.hasLength(userInfo.getPassword()))
            return 0;
        // 执行添加操作
        int result = userService.save(userInfo);
        System.out.println("add 受影响的行数:" + result);
        int num = 10 / 0; // 此处设置一个异常
        return result;
    }
    

    以上程序的运行结果如下:
    image.png
    当程序出现运行时异常时,我们预期的结果是事务应该实现自动回滚,也就是添加用户失败,然而当我们查询数据库时,却发现事务并未执行回滚操作,数据库的数据如下图所示:
    image.png

    2.timeout 超时

    当在 @Transactional 上,设置了一个较小的超时时间时,如果方法本身的执行时间超过了设置的 timeout 超时时间,那么就会导致本来应该正常插入数据的方法执行失败,示例代码如下:

    @Transactional(timeout = 3) // 超时时间为 3s
    @RequestMapping("/save")
    int save(UserInfo userInfo) throws InterruptedException {
        // 非空效验
        if (userInfo == null ||
            !StringUtils.hasLength(userInfo.getUsername()) ||
            !StringUtils.hasLength(userInfo.getPassword()))
            return 0;
        int result = userService.save(userInfo);
        return result;
    }
    

    UserService 的 save 方法实现如下:

    public int save(UserInfo userInfo) throws InterruptedException {
        // 休眠 5s
        TimeUnit.SECONDS.sleep(5);
        int result = userMapper.add(userInfo);
        return result;
    }
    

    以上程序的运行结果如下:
    image.png
    数据库没有正确的插入数据,如下图所示:
    image.png

    3.代码中有 try/catch

    在前面 @Transactional 的执行流程中,我们提到:当方法中出现了异常之后,事务会自动回滚。然而,如果在程序中加了 try/catch 之后,@Transactional 就不会自动回滚事务了,示例代码如下:

    @Transactional
    @RequestMapping("/save")
    public int save(UserInfo userInfo) throws InterruptedException {
        // 非空效验
        if (userInfo == null ||
            !StringUtils.hasLength(userInfo.getUsername()) ||
            !StringUtils.hasLength(userInfo.getPassword()))
            return 0;
        int result = userService.save(userInfo);
        try {
            int num = 10 / 0; // 此处设置一个异常
        } catch (Exception e) {
        }
        return result;
    }
    

    以上程序的运行结果如下:
    image.png
    此时,查询数据库我们发现,程序并没有执行回滚操作,数据库中被成功的添加了一条数据,如下图所示:
    image.png

    4.调用类内部 @Transactional 方法

    当调用类内部的 @Transactional 修饰的方法时,事务是不会生效的,示例代码如下:

    @RequestMapping("/save")
    public int saveMappping(UserInfo userInfo) {
        return save(userInfo);
    }
    @Transactional
    public int save(UserInfo userInfo) {
        // 非空效验
        if (userInfo == null ||
            !StringUtils.hasLength(userInfo.getUsername()) ||
            !StringUtils.hasLength(userInfo.getPassword()))
            return 0;
        int result = userService.save(userInfo);
        int num = 10 / 0; // 此处设置一个异常
        return result;
    }
    

    以上代码我们在添加方法 save 中添加了 @Transactional 声明式事务,并且添加了异常代码,我们预期的结果是程序出现异常,事务进行自动回滚,以上程序的执行结果如下:
    image.png
    然而,当我们查询数据库时发现,程序执行并不符合我们的预期,添加的数据并没有进行自动回滚操作,如下图所示:
    image.png

    5.数据库不支持事务

    我们程序中的 @Transactional 只是给调用的数据库发送了:开始事务、提交事务、回滚事务的指令,但是如果数据库本身不支持事务,比如 MySQL 中设置了使用 MyISAM 引擎,那么它本身是不支持事务的,这种情况下,即使在程序中添加了 @Transactional 注解,那么依然不会有事务的行为,这就是巧妇也难为无米之炊吧。

    总结

    当声明式事务 @Transactional 遇到以下场景时,事务会失效:

    1. 非 public 修饰的方法;
    2. timeout 设置过小;
    3. 代码中使用 try/catch 处理异常;
    4. 调用类内部 @Transactional 方法;
    5. 数据库不支持事务。

    参考 & 鸣谢

    www.cnblogs.com/frankyou/p/12691463.html

    是非审之于己,毁誉听之于人,得失安之于数。

    公众号:Java面试真题解析

    面试合集:https://gitee.com/mydb/interview

  • 相关阅读:
    前端面经 click在ios上有300ms延迟,原因及如何解决
    yolov7添加注意力机制
    【Kurbernetes部署】知识点总结(部署方式、网络插件.....)
    flink udtaf 常年不能用
    [强网杯 2022]factor有感
    【数据结构初阶】栈和队列(C语言实现+图解)
    订单30分钟自动关闭的五种解决方案
    DB2和mysql活动日志满了会自动回滚最早未提交的事务吗?
    【计算机学习】-- 电脑的组装和外设
    【算法系列 | 8】深入解析查找算法之—二分查找
  • 原文地址:https://www.cnblogs.com/vipstone/p/16694315.html