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


猜你喜欢
- mybatis-plus作为mybatis的增强工具,简化了开发中的数据库操作。一旦遇到left join或right join的左右连接,
- 访问静态资源的配置及顺序今天在玩SpringBoot的demo的时候,放了张图片在resources目录下,启动区访问的时候,突然好奇是识别
- 消息过滤RocketMQ分布式消息队列的消息过滤方式有别于其它MQ中间件,是在Consumer端订阅消息时再做消息过滤的。RocketMQ这
- 前言easyui是一种基于jQuery的用户界面插件集合。easyui为创建现代化,互动,JavaScript应用程序,提供必要的功能。使用
- Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spri
- 上一篇说的CountDownLatch是一个计数器,类似线程的join方法,但是有一个缺陷,就是当计数器的值到达0之后,再调用CountDo
- 可编程的配置方式-1如果在配置cfg.xml的时候,不想在里面配置hbm.xml怎么办呢?可在程序里使用可编程的配置方式,也就是使用程序来指
- 前言:2022年Java将有什么新的特性和改进,我相信很多Java开发者都想知道。结合Java语言架构师布莱恩·格茨(
- 这篇总结的形式是提出个问题,然后给出问题的答案。这是目前学习知识的一种尝试,可以让学习更有目的。Q1.什么时候应当重写对象的equals方法
- Android N 可以同时显示多个应用窗口。 在手机上,两个应用可以在“分屏”模式中左右并排或上下并排显示。例如,用户可以 在上面窗口聊Q
- 这篇文章主要介绍了Java利用读写的方式实现音频播放代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需
- 1.servlet:定义:接口2.配置servlet:public class HelloServlet extends HttpServl
- 本文实例讲述了C#语音识别用法。分享给大家供大家参考。具体分析如下:C#可以利用微软操作系统自动的语音识别功能,读取信息。步骤如下:1.&n
- 1. 创建自定义 * 类并实现 HandlerInterceptor 接口package com.xgf.online_mall.inter
- 本文实例为大家分享了C语言实现简单弹跳小球的具体代码,供大家参考,具体内容如下本节利用 printf 函数 实现一个在屏幕上弹跳的小球,内容
- 本文介绍在Java编程时,如何快速的构造一个XML片段,然后再将这个XML输出出来。在日常使用Java开发时,经常会用到XML。XML用起来
- springmvc @RequestBody String类型参数通过如下配置: <bean id="mapp
- 从今天开始,本专栏持续更新Android简易实战类博客文章。和以往专栏不同,此专栏只有实例。每个实例尽量按照知识点对应相应一章节的内容去写,
- 样式如下所示:布局:layoutdialog_set_pwd.xml<?xml version="." encod
- 微信公众号提供了微信支付、微信优惠券、微信H5红包、微信红包封面等等促销工具来帮助我们的应用拉新保活。但是这些福利要想正确地发放到用户的手里