作者:溪~源

来源:blog.csdn.net/xuan_lu/article/details/107797505

实际项目开发中,如果涉及到多张表操作时,为了保证业务数据的一致性,大家一般都会采用事务机制;好多小伙伴可能只是简单了解一下,遇到事务失效的情况,便会无从下手,溪源此篇文章给大家整理了一下常见Spring事务失效的场景,希望开发过程尽量避免踩坑,造成时间精力的浪费。

溪源按照最基本的使用方式以及常见失效场景优先级整理,先简单介绍一下具体失效场景:

  • 注解@Transactional配置的方法非public权限修饰;

  • 注解@Transactional所在类非Spring容器管理的bean;

  • 注解@Transactional所在类中,注解修饰的方法被类内部方法调用;

  • 业务代码抛出异常类型非RuntimeException,事务失效;

  • 业务代码中存在异常时,使用try…catch…语句块捕获,而catch语句块没有throw new RuntimeExecption异常;(最难被排查到问题且容易忽略)

  • 注解@Transactional中Propagation属性值设置错误即Propagation.NOT_SUPPORTED(一般不会设置此种传播机制)

  • mysql关系型数据库,且存储引擎是MyISAM而非InnoDB,则事务会不起作用(基本开发中不会遇到);

下面基于以上场景,溪源给小伙伴们详细解释;

非public权限修饰

参考Spring官方文档介绍,摘要、译文如下:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

译文

使用代理时,您应该只将@Transactional注释应用于具有公共可见性的方法。如果使用@Transactional注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。

简言之:@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

目前,如果@Transactional注解作用在非public方法上,编译器也会给与明显的提示,如图:

非Spring容器管理的bean

基于这种失效场景,有工作经验的大佬基本上是不会存在这种错误的;@Service 注解注释,StudentServiceImpl 类则不会被Spring容器管理,因此即使方法被@Transactional注解修饰,事务也亦然不会生效。

简单举例如下:

/*** @Author:qxy*/
//@Service
public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate ClassService classService;@Override@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void insertClassByException(StudentDo studentDo) throws CustomException {studentMapper.insertStudent(studentDo);throw new CustomException();}
}

注解修饰的方法被类内部方法调用

这种失效场景是我们日常开发中最常踩坑的地方;在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。为什么会失效呢?:

其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。

搜索公众号侠梦的开发笔记后台回复“干货”,获取一份惊喜礼包。

@Service
public class ClassServiceImpl implements ClassService {@Autowiredprivate ClassMapper classMapper;public void insertClass(ClassDo classDo) throws CustomException {insertClassByException(classDo);}@Override@Transactional(propagation = Propagation.REQUIRED)public void insertClassByException(ClassDo classDo) throws CustomException {classMapper.insertClass(classDo);throw new RuntimeException();}
}//测试用例:
@Testpublic void insertInnerExceptionTest() throws CustomException {classDo.setClassId(2);classDo.setClassName("java_2");classDo.setClassNo("java_2");classService.insertClass(classDo);}

测试结果:

java.lang.RuntimeExceptionat com.qxy.common.service.impl.ClassServiceImpl.insertClassByException(ClassServiceImpl.java:34)at com.qxy.common.service.impl.ClassServiceImpl.insertClass(ClassServiceImpl.java:27)at com.qxy.common.service.impl.ClassServiceImpl$$FastClassBySpringCGLIB$$a1c03d8.invoke(<generated>)

虽然业务代码报错了,但是数据库中已经成功插入数据,事务并未生效;

解决方案

类内部使用其代理类调用事务方法:以上方法略作改动

public void insertClass(ClassDo classDo) throws CustomException {
//         insertClassByException(classDo);((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);}
//测试用例:@Testpublic void insertInnerExceptionTest() throws CustomException {classDo.setClassId(3);classDo.setClassName("java_3");classDo.setClassNo("java_3");classService.insertClass(classDo);}

业务代码抛出异常,数据库未插入新数据,达到我们的目的,成功解决一个事务失效问题;

数据库数据未发生改变;

注意:一定要注意启动类上要添加@EnableAspectJAutoProxy(exposeProxy = true)注解,否则启动报错:

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)at com.qxy.common.service.impl.ClassServiceImpl.insertClass(ClassServiceImpl.java:28)

异常类型非RuntimeException

这种事务失效场景也是非常难排查问题的,如果没有深究源码实现,估计要花费一番功夫啦;

@Service
public class ClassServiceImpl implements ClassService {@Autowiredprivate ClassMapper classMapper;//    @Override
//    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)public void insertClass(ClassDo classDo) throws Exception {
//        即使此处使用代理对象调用内部事务方法,数据依然未发生回滚,事务机制亦然失效((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);}@Override@Transactional(propagation = Propagation.REQUIRED)public void insertClassByException(ClassDo classDo) throws Exception {classMapper.insertClass(classDo);//抛出非RuntimeException类型throw new Exception();}
//测试用例:@Testpublic void insertInnerExceptionTest() throws Exception {classDo.setClassId(3);classDo.setClassName("java_3");classDo.setClassNo("java_3");classService.insertClass(classDo);}
}

运行结果:

业务代码抛出异常,但是数据库发生更新操作;

java.lang.Exceptionat com.qxy.common.service.impl.ClassServiceImpl.insertClassByException(ClassServiceImpl.java:35)at com.qxy.common.service.impl.ClassServiceImpl$$FastClassBySpringCGLIB$$a1c03d8.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

数据库依然插入数据,不是我们想要的结果啊,赶紧修改吧,产品经理来追啦~

解决方案:

@Transactional注解修饰的方法,加上rollbackfor属性值,指定回滚异常类型:@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)

@Override@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)public void insertClassByException(ClassDo classDo) throws Exception {classMapper.insertClass(classDo);throw new Exception();}

捕获异常后,却未抛出异常

在事务方法中使用try-catch,导致异常无法抛出,自然会导致事务失效。

@Service
public class ClassServiceImpl implements ClassService {@Autowiredprivate ClassMapper classMapper;//    @Overridepublic void insertClass(ClassDo classDo) {((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);}@Override@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)public void insertClassByException(ClassDo classDo) {classMapper.insertClass(classDo);try {int i = 1 / 0;} catch (Exception e) {e.printStackTrace();}}
}// 测试用例:@Testpublic void insertInnerExceptionTest() {classDo.setClassId(4);classDo.setClassName("java_4");classDo.setClassNo("java_4");classService.insertClass(classDo);}

执行结果:

解决方案:捕获异常并抛出异常

@Override@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)public void insertClassByException(ClassDo classDo) {classMapper.insertClass(classDo);try {int i = 1 / 0;} catch (Exception e) {e.printStackTrace();throw new RuntimeException();}}

事务传播行为设置异常

此种事务传播行为不是特殊自定义设置,基本上不会使用Propagation.NOT_SUPPORTED,不支持事务

@Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)public void insertClassByException(ClassDo classDo) {classMapper.insertClass(classDo);try {int i = 1 / 0;} catch (Exception e) {e.printStackTrace();throw new RuntimeException();}}

数据库存储引擎不支持事务

以MySQL关系型数据为例,如果其存储引擎设置为 MyISAM,则事务失效,因为MyISMA 引擎是不支持事务操作的;

故若要事务生效,则需要设置存储引擎为InnoDB ;目前 MySQL 从5.5.5版本开始默认存储引擎是:InnoDB;

回复干货】获取精选干货视频教程

回复加群】加入疑难问题攻坚交流群

回复mat】获取内存溢出问题分析详细文档教程

回复赚钱】获取用java写一个能赚钱的微信机器人

回复副业】获取程序员副业攻略一份

戳这儿

你否有遇到Spring事务失效,花费太多时间找bug相关推荐

  1. 详细整理Spring事务失效的具体场景及解决方案

    实际项目开发中,如果涉及到多张表操作时,为了保证业务数据的一致性,大家一般都会采用事务机制:好多小伙伴可能只是简单了解一下,遇到事务失效的情况,便会无从下手,溪源此篇文章给大家整理了一下常见Sprin ...

  2. 面试官:Spring事务失效的场景有哪些?如何解决?

     作者:溪~源 blog.csdn.net/xuan_lu/article/details/107797505 实际项目开发中,如果涉及到多张表操作时,为了保证业务数据的一致性,大家一般都会采用事务机 ...

  3. Spring 事务失效?看这篇文章就够了!

    欢迎关注方志朋的博客,回复"666"获面试宝典 用 Spring 的 @Transactional 注解控制事务有哪些不生效的场景? 不知道小伙伴们有没有这样的经历,在自己开心的编 ...

  4. Spring事务失效 -方法内部调用

    首先感谢网友的文章 Spring事务失效的2种情况 JDK动态代理给Spring事务埋下的坑 前提知识: 两个前提 1 注解使用 spring容器的事务管理注解 @org.springframewor ...

  5. Spring 事务失效的 8 大场景,看看你都遇到过几个?

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/Yang_yangyang/ article/details/114359881 用 Spring 的 @Transaction ...

  6. spring事务失效了? @Transactional不管用了 ?看看这些@Transation的坑

    spring事务失效了? @Transactional不管用了 ?看看这些@Transactional的坑 文章目录 spring事务失效了? @Transactional不管用了 ?看看这些@Tra ...

  7. 8个Spring事务失效的场景,你碰到过几种?

    前言 作为Java开发工程师,相信大家对Spring种事务的使用并不陌生.但是你可能只是停留在基础的使用层面上,在遇到一些比较特殊的场景,事务可能没有生效,直接在生产上暴露了,这可能就会导致比较严重的 ...

  8. 详解spring事务失效和回滚失败的场景

    详解spring事务失效和回滚失败的场景 详解spring事务失效和回滚失败的场景 前言 一 .事务不生效 1.访问权限问题 2. 方法用final修饰 3.方法的内部调用 3.1 新加一个Servi ...

  9. 聊聊Spring事务失效的12种场景,太坑人了

    前言 对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了. 在某些业务场景下,如果一个请求中,需要同时写入多张表的数据.为了保证操作的原子性(要么同时成功,要么同时失败),避免数据 ...

最新文章

  1. 增量式爬虫与分布式爬虫
  2. SpringBoot整合MongoDB(实现一个简单缓存)
  3. 5G NR上行控制信道PUCCH
  4. jQuery 重要模块 回顾
  5. Python中yield
  6. 开源作者去世后,代码谁来继承?
  7. 什么是面向对象_什么是面向对象?新手程序员必掌握的技能
  8. onclick传参数
  9. 如何卸载ubuntu下的terminator
  10. 【Java】判断学生成绩等级
  11. eclipse创建测试apk文件的测试工程,报错java.lang.NullPointerException
  12. 电机振动噪声(NVH)——整数槽分数槽谐波分析补充
  13. Java模拟醉汉行走问题_醉汉随机行走/随机漫步问题(Random Walk Drunk Python)
  14. undefind_undefined
  15. Linux编写脚本nsum求和,shell脚本学习与总结
  16. 以太坊漏洞分析————4、底层函数误用漏洞
  17. 【数模】数据统计中的峰度与偏度
  18. 简单实现基于 STM32F407+ESP8266+RFID 的物联网小项目
  19. 阿里云 Windows Server 2012 R2 使用FileZilla Server 快速搭建FTP服务器
  20. local class incompatible: stream classdesc serialVersionUID = 4696092613551164015, local

热门文章

  1. python写对象提供的方法_下面哪些是python读对象提供的“写”方法?
  2. 在Ubuntu 16.04下安装g2o
  3. python跳一跳脚本详解_跳一跳 python脚本 改进版
  4. 容器和虚拟机水火不容?不存在的!
  5. 错误集锦:在安装docker的时候,发现无法安装(没有可用软件包 docker-ce。 错误:无须任何处理)
  6. 计算机函数sumif求平均值,『如何用sumif求平均年龄』excel表中如何算平均数及标准差...
  7. 翻译文本的软件有哪些?这几个翻译工具你可以试试看
  8. Android Studio 使用中 遇到的那些奇奇怪怪的问题
  9. Matlab Simulink 代数环相关问题
  10. 【二叉树】求根节点到叶节点数字之和