一个程序中不可能没有事务,而 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 遇到以下场景时,事务会失效:

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

参考 & 鸣谢

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

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

公众号:Java面试真题解析

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

面试突击83:什么情况会导致@Transactional事务失效?的更多相关文章

  1. spring boot:方法中使用try...catch导致@Transactional事务无效的解决(spring boot 2.3.4)

    一,方法中使用try...catch导致@Transactional事务无效的解决方法 1,问题的描述: 如果一个方法添加了@Transactional注解声明事务, 而方法内又使用了try catc ...

  2. 面试突击60:什么情况会导致 MySQL 索引失效?

    为了验证 MySQL 中哪些情况下会导致索引失效,我们可以借助 explain 执行计划来分析索引失效的具体场景. explain 使用如下,只需要在查询的 SQL 前面添加上 explain 关键字 ...

  3. SpringMvc配置 导致实事务失效

    SpringMVC回归MVC本质,简简单单的Restful式函数,没有任何基类之后,应该是传统Request-Response框架中最好用的了. Tips 1.事务失效的惨案 Spring MVC最打 ...

  4. 面试突击82:SpringBoot 中如何操作事务?

    在 Spring Boot 中操作事务有两种方式:编程式事务或声明式事务,接下来我们一起来看二者的具体实现. 1.编程式事务 在 Spring Boot 中实现编程式事务又有两种实现方法: 使用 Tr ...

  5. 面试突击84:Spring 有几种事务隔离级别?

    Spring 中的事务隔离级别和数据库中的事务隔离级别稍有不同,以 MySQL 为例,MySQL 的 InnoDB 引擎中的事务隔离级别有 4 种,而 Spring 中却包含了 5 种事务隔离级别. ...

  6. spring @Transactional事务失效

    不开事务几种情形 ① @Transactional写在了private方法上 org.springframework.transaction.interceptor.AbstractFallbackT ...

  7. @Transactional 注解失效场景

    @Transactional可以用在接口.类.类方法上. 作用于类:当把@Transactional注解放在类上时,表示该类的所有public方法都配置了该事物注解. 作用于方法:表示该方法配置了事物 ...

  8. 《【面试突击】— Redis篇》-- Redis的主从复制?哨兵机制?

    能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注左上角编程大道公众号,让我们一同坚持心中所想,一起成长!! <[面试突击]— Redis篇>-- Redis的主从复制?哨兵机制? 在这个 ...

  9. 《【面试突击】— Redis篇》-- Redis哨兵原理及持久化机制

    能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! <[面试突击]— Redis篇>-- Redis哨兵原理及持久化机制 在这个系列里, ...

随机推荐

  1. 一文看完vue3的变化之处

    在通读了vue的官网文档后,我记录下了如下这些相对于2.x的变化之处. 1.创建应用实例的变化 之前一般是这样: let app = new Vue({ // ...一些选项 template: '' ...

  2. 为什么不建议使用自定义Object作为HashMap的key?

    此前部门内的一个线上系统上线后内存一路飙高.一段时间后直接占满.协助开发人员去分析定位,发现内存中某个Object的量远远超出了预期的范围,很明显出现内存泄漏了. 结合代码分析发现,泄漏的这个对象,主 ...

  3. Elasticsearch面试题

    Elasticsearch面试题 1.Elasticsearch是如何实现master选举的? 1.对所有可以成为master的节点根据nodeId排序,每次选举每个节点都把自己所知道节点排一次序,然 ...

  4. scrapy框架入门

    scrapy迄今为止依然是世界上最好用,最稳定的爬虫框架,相比于其他直接由函数定义的程序, scrapy使用了面向对象并对网页请求的过程分成了很多个模块和阶段,实现跨模块和包的使用,大大提升了代码的稳 ...

  5. Git的历史和安装Git及环境配置

    Git历史同生活中的许多伟大事物一样,Git 诞生于一个极富纷争大举创新的年代. Linux 内核开源项目有着为数众广的参与者.绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务 ...

  6. centos 7系统安装

    1.打开VMware软件,点击创建虚拟机,默认选择,点击下一步 2.选择稍后安装,点击下一步 3.在Linux系统中选择CentOS 7 64位,点击下一步 4.选择好安装位置后,点击下一步 5.选择 ...

  7. DDS信号发生器加强版(双通道,发送波形的频率可控,相位可控,种类可控)

    目的:设计一个DDS,可以输出两个波形,输出的波形的周期可以修改,相位可以修改,种类也可以修改 输入:clk,reset,一个控制T的按键,一个控制相位的按键,一个控制波形种类的按键. 思路:双通道- ...

  8. day01--DOS常用命令

    打开CMD的方式 开始+系统+命令提示符 Win键+R输入cmd打开控制台(推荐使用) 在任意的文件夹下面,按住shift键+鼠标右键点击,在此处打开命令行窗口 资源管理器的地址栏前面加,上cmd路径 ...

  9. C++ 加速(卡常)技巧【超级 快读、快写】

    C++ \texttt{C++} C++ 加速技巧 快读快写 快读 inline int read() { int x = 0, w = 0; char ch = 0; while (!isdigit ...

  10. ASP.NET Core 产生连续 Guid

    1 前言 1.1 这篇文章面向的读者 本文不会过多解释 Guid 是什么,以及顺序 Guid 的作用,需要读者自行具备: 知道 Guid,并且清楚其作用与优势 清楚 Guid.NetGuid() 产生 ...