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

serviceprivateFunctionCaller方法从而间接调用标注了@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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/418934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 23种设计模式

https://www.cnblogs.com/malihe/p/6891920.html https://www.runoob.com/design-pattern/factory-pattern.html https://www.cnblogs.com/JavaHxm/p/11016315.html

git多分支频繁切换导致IDEA工具maven工程项无法识别java文件的一种解决方法

由于频繁的进行git多分支的切换,导致本地maven工程依赖切换失败,进而导致项目无法识别java工具,多次rebuild项目均以失败而告终。 提供一种有效的正确方式: 在IDEA的terminal输入命令 mvn clean mvn install -Dmaven.test.skipt…

MySQL中去除字段中的回车符和换行符

https://blog.csdn.net/u012586848/article/details/50997865

简说SQLite

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。什么是ACID?指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性&#xff0…

MySQL中count(*)用法

count()函数:一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值 对不同count()统计函数…

Java “\”与\\的替换

Java replaceAll方法中,必须用“\\”表达一个"",因为Java中“\”是转义字符,通常会误以为“\”就能表示"",实际上,replaceAll算法的实现运用了正则表达式,所以这里经历了两次转化,即是replaceAll(…

PHP闭包(Closure)初探(转载 http://my.oschina.net/melonol/blog/126694?p=2#comments)

匿名函数 提到闭包就不得不想起匿名函数,也叫闭包函数(closures),貌似PHP闭包实现主要就是靠它。声明一个匿名函数是这样: ?123$func function() {}; //带结束符可以看到,匿名函数因为没有名字&#xff0…

bootstrap 学习网址

http://edu.jb51.net/bootstrap/bootstrap-tables.html

手把手教你--JAVA微信支付(H5支付)

http://www.pianshen.com/article/901316384/ https://blog.csdn.net/qq_16927377/article/details/80542682

寻找子串

中国电信2016年IT研发工程师笔试题 12 给定一个已经排好序的字符串数组,空字符串散布在该数组中,编写一个函数寻找一个 给定字符串的位置。 解法:循环搜索第一个字符,第一个匹配则进行统计个数,当匹配个数等于子串长度时,则可以输…

css教程

https://www.runoob.com/css/css-tutorial.html

当下大部分互联网创业公司为什么都愿意采用增量模型来做开发?

现在互联网正在飞速的发展,各种各样的互联网创业公司如雨后春笋般的涌现。而在互联网初创企业中广泛运用的增量模型无疑是大家关注的重中之重,本文主要谈谈我个人对增量模型在互联网创业公司得到推广原因的初步理解。 1.把目标软件拆分为实现其部分功能增…

beetl 取list下标的问题

[DEBUG] 11:44:23.194 org.beetl.ext.nutz.LogErrorHandler.processExcption(LogErrorHandler.java:32) - 属性访问出错 11:44:23:属性获取异常(ATTRIBUTE_INVALID):[] 位于21行 资源:/platform/wage/calculation/detail/edit.html 属性访问出错 18| 19| <%for(item in map[…

横竖屏切换时候Activity的生命周期的总结

1、新建一个Activity&#xff0c;并把各个生命周期打印出来 2、运行Activity&#xff0c;得到如下信息 onCreate--> onStart--> onResume--> 3、按crtlf12切换成横屏时 onSaveInstanceState--> onPause--> onStop--> onDestroy--> onCreate--> onStart…

bootstrp-table 获取checkbox选中行的数据id

https://blog.csdn.net/qq_20603425/article/details/84253782

软件工程个人作业01;

设计思路“ 1 for循环30次&#xff1b; 2 首先随机输出运算符 2.2 String数组a[]{”“&#xff0c;”*”&#xff0c;“—”&#xff0c;“”} 2.3 随机输出0--3&#xff08;c表示)中整数&#xff0c;做为数组标号da[c] 2.4整数运算随机数出0--99之间整数 3.进行分数和整数区别…

oracle数据库安装跳坑

安装版本 &#xff1a;oracle 11g 常规安装就可以了。需要注意的是有篇教程里写到出现“不满足最低配置的时候”要进行相关代码修改。我第一次是这样做&#xff0c;后来进入数据库出现了错误&#xff0c;第二次安装&#xff0c;我直接忽略他的提示&#xff0c;进行常规的安装&a…

$.each()、$.map()区别浅谈

https://www.cnblogs.com/jinxing-tc/p/9568832.html