Spring的@Transactional注解踩坑
@Transactional介绍
Spring
为开发人员提供了声明式事务的使用方式,即在方法上标记@Transactional
注解来开启事务。大家在日常的开发中很多业务代码对数据进行操作的时候一定是希望有事务控制的。
比如电商卖东西业务,代码的逻辑是商家先生成一个订单(订单信息插入到数据库中),再将钱收入到自己的账户中(数据库中的money增加)。整个过程是要作为一个完整的事务来对待的,如果后面这个操作失败了,那么前者也一定不能插入成功,这时候就会用到事务的回滚。
@Transactional的常见错误使用姿势
抛出异常
常见错误一:异常没有传播出事务注解@Transactional标记的方法导致
经典的开发经验:
很多时候,在实际业务开发中,总希望接口能返回一个固定的类实例——这叫做统一返回结果。例如以Result
类作为统一返回结果。
于是为了方便就直接在Service
的方法中return一个Result
类对象,为了避免受异常的影响而无法返回该结果集,就会使用try-catch
语句,当业务代码出现错误而抛出异常时会捕获此异常,将异常信息写入Result的相关字段中,返回给调用者。
@Controller
@RestController
@Api( tags = "测试事务是否生效")
@RequestMapping("/test/transactionalTest")
@Slf4j
public class GoodsStockController {@Autowiredprivate GoodsStockService goodsStockService;/*** create by: entropy* description: 事务无法回滚的方法* create time: 2022/1/25 21:38*/@GetMapping("/exception/first")@ApiOperation(value = "关于异常的第一个方法,不能够回滚", notes = "因为异常未能被事务发现,所以没有回滚")@ResponseBodypublic Result firstFunctionAboutException(){try{return goodsStockService.firstFunctionAboutException();}catch (Exception e){return Result.server_error().Message("操作失败:"+e.getMessage());}}
}
其中的service中的方法
@Autowiredprivate GoodsStockMapper goodsStockMapper;@Override@Transactionalpublic Result firstFunctionAboutException() {try{log.info("减库存开始");goodsStockMapper.updateStock();if(1 == 1) throw new RuntimeException();return Result.ok();}catch (Exception e){log.info("减库存失败!" + e.getMessage());return Result.server_error().Message("减库存失败!" + e.getMessage());}}
最终测试结果事务没有回滚。我们都知道当程序执行时出现错误而抛出异常时,事务才会回滚,这里虽然出现了异常但却被方法本身消化了(catch掉了),异常没有被事务所发现,所以这样子是不会出现回滚的。因此正确的姿势是去掉service
中的try-catch
语句即可:
@Override
@Transactional
public void secondFunctionAboutException() {log.info("减库存开始");goodsStockMapper.updateStock();if(1 == 1) throw new RuntimeException();
}
通过这种处理方式可以实现事务的回滚。另外异常怎么办呢?很简单,将异常放在Controller层去处理就行。
总结:当标记了@Transactional
注解的方法中出现异常时,如果该异常未传播到该方法外,则事务不会回滚;反之,只有异常传播到该方法之外,事务才会回滚。
常见错误二:异常突破@Transactional
所标注的方法,事务依然没有回滚
@Override
@Transactional
public void thirdFunctionAboutException() throws Exception {log.info("减库存开始");goodsStockMapper.updateStock();if(1 == 1) throw new Exception();
}
眼尖的同学一眼就看到了问题:
Spring
的@Transactional
注解就是默认只有当抛出RuntimeException
运行时异常时,才会回滚。
Spring
通常采用RuntimeException
表示不可恢复的错误条件。也就是说对于其他异常,Spring
觉得无所谓所以就不回滚。
那么对应的解法也有2种,请看:
解法一:手动catch捕获Exception,然后抛出runtimeException
@Override
@Transactional
public void thirdFunctionAboutException1(){try{log.info("减库存开始");goodsStockMapper.updateStock();if(1 == 1) throw new Exception();}catch (Exception e){log.info("出现异常"+e.getMessage());throw new RuntimeException("手动抛出RuntimeException");}
}
解法二:修改注解默认的@Transactional
回滚的异常范围
@Override
@Transactional(rollbackFor = Exception.class)
public void thirdFunctionAboutException2() throws Exception {log.info("减库存开始");goodsStockMapper.updateStock();if(1 == 1) throw new Exception();
}
更高级的错误使用姿势
假设service业务类中有这样的2个方法:
@Override
public void privateFunctionCaller (){privateCallee();
}@Transactional
private void privateCallee(){goodsStockMapper.updateStock();throw new RuntimeException();
}
service
的privateFunctionCaller
方法从而间接调用标注了@Transactional
注解的方法privateCallee
。执行代码后,发现事务并没有回滚。这是什么原因呢?
这就要提到@service注解的原理:spring是通过动态代理的方式来实现AOP的。也即AOP容器中的bean实际上都是代理对象。@Transactional注解的支持也正是通过AOP来实现的。Spring
会对原对象中的方法进行封装(即检查到标有该注解的方法时,就会为它加上事务).。这个行为就叫做为目标方法进行增强。要想铜鼓增强的方式使得事务生效,那么方法必然不能是private的,实际上如果在ieda编辑器里在私有的方法上使用了@Transactional注解的话,编译器是会报错的。
实际上修改为public后事务还是没有回滚。这是为什么呢?
@Override
public void publicFunctionCaller (){publicCallee();
}@Override
@Transactional
public void publicCallee(){goodsStockMapper.updateStock();throw new RuntimeException();
}
被注入的Service
对象是代理对象,当调用publicCallee
方法时,上面是没有@Transactional
注解的。故只是简单执行service.function()
,即在代理对象的方法publicFunctionCaller
中,先由Service的原对象来调用自己的publicFunctionCaller
方法,再由其调用自己的publicCallee
方法。不会走代理对象增强过(带有事务)的publicCallee
方法,事务也就不会回滚。
解决办法:显式的注入自己。缺点就是破坏了分层的结构,加大了代码耦合性
@Override
@Transactional
public void publicCallee(){goodsStockMapper.updateStock();throw new RuntimeException();
}@Autowired
private GoodsStockService self;@Override
public void aopSelfCaller (){self.publicCallee();
}
Spring的@Transactional注解踩坑相关推荐
- Spring的Transactional注解
Spring的Transactional注解主要有以下功能: 1. 标注在方法上,如果该方法掉了多个别的方法,每个方法都有对数据库做数据更改,如果这些更改需要保持一致性,这时就可以用到这个注解. 2. ...
- 聊一聊Spring中@Transactional注解及其失效的七种场景
文章目录 一.事务(基于AOP) 二.@Transactional介绍 三.@Transactional失效场景 说明:当我准备写我知道的那几个场景时,我发现有人比我写的更好,关键是好得多,于是我就用 ...
- spring的@Transactional注解详细用法
概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性. Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型 ...
- SSM框架中使用Spring的@Transactional注解进行事务管理
一 介绍 在企业级应用中,保护数据的完整性是非常重要的一件事.因此不管应用的性能是多么的高.界面是多么的好看,如果在转账的过程中出现了意外导致用户的账号金额发生错误,那么这样的应用程序也是不可接受的 ...
- SpringCloud学习笔记009---杂七杂八003:spring的@Transactional注解详细用法
概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性. Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型 ...
- Spring Boot Transactional注解源码阅读笔记(二)
在源码笔记(一)中,我们留下了几个问题: Spring Boot是怎么扫描到我们的bean里面有 Transactional 这个注解,并且把 InfrastructureAdvisorAutoP ...
- Spring:@Transactional 注解使用讲解
文章目录 1.美图 2. 源码 3.@Transactional 注解的属性信息 4.事务传播机制 5.注意 6. 案例 6.1 基于tx标签的声明式事物 6.2 基于@Transactional注解 ...
- Spring学习总结(33)—— 用 Spring 的 @Transactional 注解控制事务有哪些不生效的场景?
数据库引擎不支持事务 这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB.根据 MySQL 的官方文档: htt ...
- Spring Cloud Gateway之踩坑日记
目录 一.背景 二.神秘的超时 坑一:通过SCG的GlobalFilter记录的网关处理耗时不准 坑二:reactor-netty的epoll&kqueue模式 坑三:SCG的同步更新路由信息 ...
最新文章
- MPB:林科院袁志林组-​原生质体法制备根系腐生型共生菌(伞菌目)单核化菌丝...
- Xshell 连接ubuntu16.04 32位
- 回归分析结果表格怎么填_手把手教绘制回归分析结果的森林图GraphPad Prism和Excel...
- 在界面中显示文本内容
- 彻底与高通谈崩?苹果被爆计划自研5G调制解调器
- 打工人打工魂,打工人上人
- python解释器安装过程
- tomcat官网下载详细步骤
- EBS 采购订单入库
- html站点根目录怎么改,通过重定向把子目录设置为网站根目录
- android工程文件assts,应用程序基础androiddevelopers英文翻译本科论文.docx
- 2022.01.19 - SX10-23.零钱兑换
- 制作小地图MiniMap小结
- Elasticsearch项目实战,商品搜索功能设计与实现!
- 如何学习黑客技术?初级黑客入门
- web应用商城部署(gpmall)
- VR全景的拍摄与制作
- python3入门到精通 pdf_解析《Python3标准库》PDF中英文代码+《算法之美指导工作与生活的算法》PDF中英文+《Scratch编程从入门到精通PDF》趣学...
- 从源码分析LinkedList集合
- 利用 MSYS2 及osgEarth 构建三维地球模型(1) 软件配置
热门文章
- shiro学习(21):动态添加验证规则1
- spring mvc学习(43):处理静态资源
- 第八十六期:“程序员锁死服务器导致公司倒闭”案正式开庭审理
- java学习(86):Interage方法compareto,parseint,intvalue
- Vue之父组件向子组件传递数据
- 关于python变量的描述_Python变量命名的详细介绍
- ftp改为sftp_科普!一文详解 FTP、FTPS 与 SFTP 的原理
- 【USACO15DEC】最大流Max Flow
- Java面向对象---重写(Override)与重载(Overload)
- UUID工具类及使用