Spring @Transactional事务失效的原因分析

 更新时间:2022年09月15日 09:07:54   作者:Java中文社群  
一个程序中不可能没有事务,Spring中,事务的实现方式分为两种:编程式事务和声明式事务。日常项目中,我们都会使用声明式事务 @Transactional来实现事务,本文来和大家聊聊什么情况会导致@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;
}

以上程序的运行结果如下:

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

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;
}

以上程序的运行结果如下:

数据库没有正确的插入数据,如下图所示:

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;
}

以上程序的运行结果如下:

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

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 声明式事务,并且添加了异常代码,我们预期的结果是程序出现异常,事务进行自动回滚,以上程序的执行结果如下:

然而,当我们查询数据库时发现,程序执行并不符合我们的预期,添加的数据并没有进行自动回滚操作,如下图所示:

5.数据库不支持事务

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

总结

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

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

到此这篇关于Spring @Transactional事务失效的原因分析的文章就介绍到这了,更多相关Spring @Transactional事务失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaWeb Listener 利用Session统计在线人数

    JavaWeb Listener 利用Session统计在线人数

    这篇文章主要为大家介绍了JavaWeb Listener 利用Session统计在线人数,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Java打印斐波那契前N项的实现示例

    Java打印斐波那契前N项的实现示例

    这篇文章主要介绍了Java打印斐波那契前N项的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • 关于java获取新浪天气示例

    关于java获取新浪天气示例

    这篇文章主要介绍了关于java获取新浪天气示例,新浪的接口可以直接通过城市名字查询天气所以这里使用新浪API,,需要的朋友可以参考下
    2023-04-04
  • 浅谈二分法查找和原始算法查找的效率对比

    浅谈二分法查找和原始算法查找的效率对比

    这篇文章主要介绍了浅谈二分法查找和原始算法查找的效率对比,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • Java9中操作和查询本地进程信息的示例详解

    Java9中操作和查询本地进程信息的示例详解

    这篇文章主要为大家详细介绍了Java9中操作和查询本地进程信息的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • datax-web在windows环境idea中模块化打包部署操作步骤

    datax-web在windows环境idea中模块化打包部署操作步骤

    这篇文章主要介绍了datax-web在windows环境idea中模块化打包部署操作步骤,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • java实现桌球游戏

    java实现桌球游戏

    这篇文章主要为大家详细介绍了java实现桌球游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • Spring Boot与Redisson实时排行榜功能

    Spring Boot与Redisson实时排行榜功能

    排行榜功能是常见且重要的需求之一,本文主要介绍了Spring Boot与Redisson实时排行榜功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • mybatisplus添加真正的批量新增、批量更新的实现

    mybatisplus添加真正的批量新增、批量更新的实现

    这篇文章主要介绍了mybatisplus添加真正的批量新增、批量更新的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 记录一次connection reset 错误的解决全过程

    记录一次connection reset 错误的解决全过程

    这篇文章主要介绍了记录一次connection reset 错误的解决全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04

最新评论