spring声明式事务 @Transactional 不回滚的多种情况以及解决方案
作者:ratelfu 发布时间:2023-07-06 10:50:33
本文是基于springboot完成测试测试代码地址如下:
https://github.com/Dr-Water/springboot-action/tree/master/springboot-shiro
一、 spring 事务原理
一、Spring事务原理
在使用JDBC事务操作数据库时,流程如下:
//获取连接
1.Connection con = DriverManager.getConnection()
//开启事务
2.con.setAutoCommit(true/false);
3.执行CRUD
//提交事务/回滚事务
4. con.commit() / con.rollback();
//关闭连接
5. conn.close();
Spring本身并不提供事务,而是对JDBC事务通过AOP做了封装,隐藏了2和4的操作,简化了JDBC的应用。
spring对JDBC事务的封装,是通过AOP * 来实现的,在调用目标方法(也就是第3步)前后会通过代理类来执行事务的开启、提交或者回滚操作。
spring事务使用的两个不可忽略点:
注意关键词 “ * ”,这意味着要生成一个代理类,那么我们就不能在一个类内直接调用事务方法,否则无法代理,而且该事务方法必须是public,如果定义成 protected、private 或者默认可见性,则无法调用!
问题一、@Transactional 应该加到什么地方,如果加到Controller会回滚吗?
@Transactional 最好加到service层,加到Controller层也是生效的,但是为了规范起见,还是加到service层上。
下载代码并启动难项目进行验证:主要代码如下:
Controller层代码如下:
@Autowired
private TransactionalService transactionalService;
@Autowired
private UserDao userDao;
@Autowired
private JwtUserDao jwtUserDao;
/**
* 测试@Transactional 注解加到service层事务是否回滚
*/
@RequestMapping("/tx")
public void serviceTX(){
transactionalService.controllerTX();
}
/**
* 测试@Transactional 注解加到Controller层事务是否回滚
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/ctx2")
public void cTX2(){
userDao.update();
System.out.println(2/0);
jwtUserDao.update();
}
/**
* 测试@Transactional 注解加到Controller层事务是否回滚
* 这里在Controller层为了方便直接调用了dao层,在实际开发中dao层即可在Controller层调用也可以在service层调用,
* 比如service层只是直接调用dao层一个方法,此外没有任何操作,那么这时候完全不用写service层的方法,直接在Controller调用dao层即可,
* 当然如果公司有规范,必须严格按照mvc的模式进行开发,则另说
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/ctx2")
public void cTX2(){
userDao.update();
//手动抛出一个RuntimeException
System.out.println(2/0);
jwtUserDao.update();
}
service层的主要代码
@Autowired
private UserDao userDao;
@Autowired
private JwtUserDao jwtUserDao;
@Transactional(rollbackFor = Exception.class)
public void controllerTX(){
userDao.update();
//手动抛出一个RuntimeException
System.out.println(2/0);
jwtUserDao.update();
}
dao层sql语句如下:
<update id="update">
UPDATE jwt_user SET username ='wangwuupdate' WHERE user_id= 2
</update>
<update id="update">
UPDATE user SET username ='zsupdate' WHERE id= 2
</update>
数据库原始数据:
浏览器中输入:http://localhost:8081/tx/tx,由于本次使用测试代码进行统一的异常处理所以浏览器的返回数据如下:
控制台输出如下:
查看数据库中的数据并没有被修改
浏览器中输入:http://localhost:8081/tx/ctx2,
查看数据库中的数据并没有被修改
由此可以得出 :@Transactional 加到Controller层也是生效的,但是为了规范起见,还是加到service层上。
问题二、 @Transactional 注解中用不用加rollbackFor = Exception.class 这个属性值
spring的api doc中有折磨一句描述:
红框中的内容如下:
rolling back on RuntimeException and Error but not on checked exceptions
大致意思就默认情况下,当程序发生 RuntimeException 和 Error 的这两种异常的时候事务会回滚,但是如果发生了checkedExcetions ,如fileNotfundException 则不会回滚,所以 rollbackFor = Exception.class 这个一定要加!
验证如下:
浏览器输入:http://localhost:8081/tx/ctx3
控制台输出如下:
这时候查看数据库中的数据并没有被修改
浏览器输入:http://localhost:8081/tx/ctx4
这时候查看数据库数据已经被修改:
问题三:事务调用嵌套问题具体结果如下代码:
/**
* 同类中在方法a中调用b
* a没有事务,b有 ,异常发生在b中 不会回滚
*/
@RequestMapping("/a1")
public void a1(){
transactionalService.a1();
}
/**
* 同类中在方法a中调用b
* a没有事务,b有 ,异常发生在a中 不会回滚
*/
@RequestMapping("/a2")
public void a2(){
transactionalService.a2();
}
/**
* 同类中在方法a中调用b
* a有事务,b没有 ,异常发生在b中 会回滚
*/
@RequestMapping("/a3")
public void a3(){
transactionalService.a3();
}
/**
* 同类中在方法a中调用b
* a有事务,b没有 ,异常发生在a中 会回滚
*/
@RequestMapping("/a4")
public void a4(){
transactionalService.a4();
}
/**
* 同类中在方法a中调用b
* a有事务,b也有 ,异常发生在b中 会回滚
*/
@RequestMapping("/a5")
public void a5(){
transactionalService.a5();
}
/**
* 同类中在方法a中调用b
* a有事务,b也有 ,异常发生在a中 会回滚
*/
@RequestMapping("/a6")
public void a6(){
transactionalService.a6();
}
/**
*a类中调用b类中的方法
* a中有事务,b中也有 会回滚
*
*/
@RequestMapping("/b5")
public void b5(){
transactionalService.b5();
}
/**
*a类中调用b类中的方法
* a中有事务,b中没有 会回滚
*
*/
@RequestMapping("/b6")
public void b6(){
transactionalService.b6();
}
/**
*a类中调用b类中的方法
* a没有事务,b中有 不会回滚
*
*/
@RequestMapping("/b7")
public void b7(){
transactionalService.b7();
}
/**
*a类中调用b类中的方法
* a没有事务,b中没有 不会回滚
*
*/
@RequestMapping("/b8")
public void b8(){
transactionalService.b8();
}
总结:如果在a方法中调用b方法不管是不是a和b是不是在同一个类中,只要a方法中没有事务,则发生异常的时候不会回滚,即:当a无事务时,则a和b均没有事务,当a有事务时,b如果有事务,则b事务会加到a事务中,二者为同一事务!
四、总结
在springboot中默认是开启事务的,在service层的方法加上@Transactional(rollbackFor = Exception.class) 注解即可实现事务 如果在方法a中调用方法b 如果要实现事务,则只需要在方法上加上@Transactional(rollbackFor = Exception.class) 即可!
如果业务需要,一定要抛出checked异常的话,可以通过rollbackFor属性指定异常类型即可。有兴趣的可以动手验证一下,这里不再赘述。
五、 参考链接
Spring Boot中的事务管理
深入理解 Spring 之 SpringBoot 事务原理
声明式事务不回滚@Transactional的避坑正确使用
Spring声明式事务不回滚问题
spring 事务应用误区总结:那些导致事务不回滚的坑
你的Spring事务为什么不会自动回滚,包含异常的分类
Java异常之checked与unchecked
来源:https://blog.csdn.net/weter_drop/article/details/103582742
猜你喜欢
- java的String对象底层是有字符数组存储的,理论上char[] 最大长度是int的最大值,实际思路:首先,String字面
- 前言使用SpringBoot来开发项目相对于传统模式,要快速优雅许多,相信目前国内绝大部分web项目的开发还没有使用SpringBoot来做
- 上篇文章老王买产品 我们从最原始的基本实现方法,到简单(静态)工厂,然后使用工厂方法设计模式进行改造,最后考虑产品会产生变体,我们
- 1.引入AOP依赖<dependency>
- poi解析Excel文件版本问题解决办法poi解析Excel文件时有两种格式: HSSFWorkbook格式用来解析Excel2003(xl
- 一、算法描述波雷费密码是一种对称式密码,是首种双字母取代的加密法。下面描述算法步骤:1、从1号二维码M05,提取明文信息和密文,M05格式:
- Java7引入了Fork Join的概念,来更好的支持并行运算。顾名思义,Fork Join类似与流程语言的分支,合并的概念。也就是说Jav
- 我们通过项目的reimport等众多办法无法解决之后:假设这个是爆红的,因为被我已经解决了。我们进入到我们的本地仓库, 新建包。在repos
- 前言当系统的并发比较高的时候,日志的处理输出也是一种性能的开销负担,所以,选择一个中间件来处理消费日志必不可少!下面是spring boot
- 重写 equals()方法 和 hashCode()方法最近看了学习了集合的简单的知识,碰到了讲解 Set 的部分,感觉很好奇,这里对于 S
- 一.OO(面向对象)的设计基础面向对象(OO):就是基于对象概念,以对象为中心,以类和继承为构造机制,充分利用接口和多态提供灵活性,来认识、
- 概述java移位符主要包括3种:运算符名称>>左移运算符<<有符号右移运算符<<<无符号右移运算符
- 话不多说,请看代码:package com.lxj.demo;import java.io.BufferedReader;import ja
- 本文实例讲述了Android TextView中文字通过SpannableString设置属性的方法。分享给大家供大家参考,具体如下:在An
- 导语相信大家无论是做前端还是做后端的,都被接口接口文档所折磨过,前端抱怨接口文档和后端给的不一致,后端抱怨写接口文档很麻烦,所以Swagge
- 本文实例讲述了C++实现的O(n)复杂度内查找第K大数算法。分享给大家供大家参考,具体如下:题目:是在一组数组(数组元素为整数,可正可负可为
- 前言 因为自己在做的一个小软件里面需要用到从A-Z排序的ListView,所以自然而然的想到了微信的联系人,我想要的就是那样的效果。本来没
- Linux Hadoop 2.7.3 安装搭建Hadoop实现了一个分布式文件系统(Hadoop Distributed File Syst
- 附GitHub源码:WebViewExplore一、WebView的基础配置WebSettings ws = getSettings();w
- 无论是用Eclipse还是用Android Studio做android开发,都会接触到jar包,全称应该是:Java Archive,即j