@Transactional注解的失效场景

@Transactional注解的失效场景

引言

@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。

下面我们从what ,where,when四个方面彻底弄明白如何回答面试官的问题。

一、什么是事务(WHAT)

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

这里我们以取钱的例子来讲解:比如你去ATM机取1000块钱,大体有两个步骤:第一步输入密码金额,银行卡扣掉1000元钱;第二步从ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。

如何保证这两个步骤不会出现一个出现异常了,而另一个执行成功呢?事务就是用来解决这样的问题。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。

在我们日常开发中事务分为声明式事务和编程式事务。

编程式事务

是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。

编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。

即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。

对于编程式事务管理,spring推荐使用TransactionTemplate。

try {//TODO somethingtransactionManager.commit(status);
} catch (Exception e) {transactionManager.rollback(status);throw new InvoiceApplyException("异常");
}

声明式事务

管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;

而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

声明式事务也有两种实现方式,一是基于TXAOP的xml配置文件方式,二种就是基于@Transactional注解了。

@GetMapping("/user")
@Transactional
public String user() {int insert = userMapper.insert(userInfo);
}

二、@Transactional可以在什么地方使用(WHERE)

1、@Transactional注解可以作用于哪些地方?

@Transactional 可以作用在接口类方法

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {@Autowiredprivate UserMapper userMapper;@Transactional(rollbackFor = Exception.class)@GetMapping("/user")public String test() throws Exception {User user = new User();user.setName("javaHuang");user.setAge("2");user.setSex("2");int insert = userMapper.insert(cityInfoDict);return insert + "";}
}

2、@Transactional属性详解

propagation属性

propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
  • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
  • Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

isolation 属性

isolation :事务的隔离级别,默认值为 Isolation.DEFAULT

TransactionDefinition.ISOLATION_DEFAULT

这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是

TransactionDefinition.ISOLATION_READ_UNCOMMITTED

该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。

TransactionDefinition.ISOLATION_READ_COMMITTED

该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。

TransactionDefinition.ISOLATION_REPEATABLE_READ

该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。

TransactionDefinition.ISOLATION_SERIALIZABLE

所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

timeout 属性

timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性**

noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

二、@Transactional什么时候会失效(WHEN)

面试官就直接问我有没有用过@Transactional,我肯定不能说没用过啊,十分自信的说,常用。

面试官又问我,在实际开发过程有没有遇到过@Transactional失效的情况,我肯定不能说没有啊,再次十分自信的说到,经常。

面试官一脸问号,经常???那你给我说说@Transactional在什么时候会失效呢?

下面的内容是我将我面试时说的失效场景整理了一下。

1、@Transactional 应用在非 public 修饰的方法上

如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。

之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute`方法,获取Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Methodmethod,Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() &&!Modifier.isPublic(method.getModifiers())) {return null;
}

Modifier.isPublic会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

注意:protectedprivate 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

2、数据库引擎要不支持事务

数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的。

3、@由于propagation 设置错误,导致注解失效

在上面解读propagation 属性的时候,我们知道

TransactionDefinition.PROPAGATION_SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED

以非事务方式运行,如果当前存在事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_NEVER

以非事务方式运行,如果当前存在事务,则抛出异常。

当我们将propagation 属性设置为上述三种时,@Transactional 注解就不会产生效果

4、rollbackFor 设置错误,@Transactional 注解失效

上述我们解读rollbackFor 属性的时候我们知道

rollbackFor 可以指定能够触发事务回滚的异常类型。

Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;

其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor=MyException.class

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring源码如下:

private int getDepth(Class<?> exceptionClass, int depth) {if (exceptionClass.getName().contains(this.exceptionName)) {// Found it!return depth;
}// If we've gone as far as we can go and haven't found it...if (exceptionClass == Throwable.class) {return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}

5、方法之间的互相调用也会导致@Transactional失效

我们来看下面的场景:

比如有一个类User,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

   //@Transactional@GetMapping("/user")private Integer A() throws Exception {User user = new User();user.setName("javaHuang");/*** B 插入字段为 topJavaer的数据*/this.insertB();/*** A 插入字段为 2的数据*/int insert = userMapper.insert(user);return insert;}@Transactional()public Integer insertB() throws Exception {User user = new User();user.setName("topJavaer");return userMapper.insert(user);}

6、异常被你的 catch“吃了”导致@Transactional失效

这种情况是最常见的一种@Transactional注解失效场景,

  @Transactionalprivate Integer A() throws Exception {int insert = 0;try {User user = new User();user.setCityName("javaHuang");user.setUserId(1);/*** A 插入字段为 javaHuang的数据*/insert = userMapper.insert(user);/*** B 插入字段为 topJavaer的数据*/b.insertB();} catch (Exception e) {e.printStackTrace();}}

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚,而是会报出异常

org.springframework.transaction.UnexpectedRollbackException: Transactionrolled back because it has been marked as rollback-only

解决方法:

第一:声明事务的时候加上rollback=‘exception’

第二 :cath代码块里面手动回滚

总结

@Transactional 注解我们经常使用,但是往往我们也只是知道它是一个事务注解,很多时候遇到事务注解失效的情况下,我们都是一头雾水,看不出个所以然来,花费了很长的时间都不能解决。

通过本文了解了@Transactional 注解的失效场景,在以后遇到这种情况时,基本就能一眼看破,然后摸摸自己光滑的脑门,soga,so easy!

妈妈再也不用担心我找不到自己写的bug了。




分析spring事务@Transactional注解在同一个类中的方法之间调用不生效的原因及解决方案

问题:

在Spring管理的项目中,方法A使用了Transactional注解,试图实现事务性。但当同一个class中的方法B调用方法A时,会发现方法A中的异常不再导致回滚,也即事务失效了。

当这个方法被同一个类调用的时候,spring无法将这个方法加到事务管理中。

我们来看一下生效时候和不生效时候调用堆栈日志的对比。

img

通过对比两个调用堆栈可以看出,spring的@Transactional事务生效的一个前提是进行方法调用前经过拦截器TransactionInterceptor,也就是说只有通过TransactionInterceptor拦截器的方法才会被加入到spring事务管理中,查看spring源码可以看到,在AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice方法中会从调用方法中获取@Transactional注解,如果有该注解,则启用事务,否则不启用。

img

这个方法是通过spring的AOP类CglibAopProxy的内部类DynamicAdvisedInterceptor调用的,而DynamicAdvisedInterceptor继承了MethodInterceptor,用于拦截方法调用,并从中获取调用链。

如果是在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用,必须将方法放入另一个类,并且该类通过spring注入。

原因:

Transactional是Spring提供的事务管理注解。

重点在于,Spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。

而在同一个class中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象。所以Spring无法切到这次调用,也就无法通过注解保证事务性了。

也就是说,在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用。

解决方法1:

将事务方法放到另一个类中(或者单独开启一层,取名“事务层”)进行调用,即符合了在对象之间调用的条件。

解决方法2:

获取本对象的代理对象,再进行调用。具体操作如:

  1. Spring-content.xml上下文中,增加配置:<aop:aspectj-autoproxy expose-proxy=“true”/>

  2. 在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。

解决方法3:

很多时候,方法内调用又希望激活事务,是由于同一个方法既有DAO操作又有I/O等耗时操作,不想让耗时的I/O造成事务的太长耗时(比如新增商品同时需要写入库存)。此时,可以将I/O做成异步操作(如加入线程池),而加入线程池的操作即便加入事务也不会导致事务太长,问题可以迎刃而解。

解决方法4:

用@Autowired 注入自己 然后在用注入的bean调用自己的方法也可以

参考:
https://blog.csdn.net/ligeforrent/article/details/79996797

https://www.jianshu.com/p/2e4e1007edf2

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

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

相关文章

Spring 事务方法与非事务方法相互调用 @Transactional 注解失效不回滚?

写这篇文章的初衷呢就是最近遇到了一个Spring事务的大坑。与其说是坑&#xff0c;还不如说是自己事务这块儿太薄弱导致的&#xff08;自嘲下&#xff09;。 项目环境 Spring Boot 下面开始问题描述&#xff0c;发生的过程有点长&#xff0c;想直接看方案的直接跳过哦~&#x…

vue后端必须接口吗_前后端分离模式,后端说开发完才能给接口文档,合理吗

背景&#xff1a;汇总了下老王在其他平台的原创回复&#xff0c;欢迎关注老王原创公众号【软件老王】&#xff0c;关注不迷路。一、后端开发完接口才给出接口文档&#xff0c;合理吗&#xff1f;本人所在的项目组做项目过程中&#xff0c;后端不会先给出接口文档&#xff0c;而…

JAVA那点破事,并发、IO模型、集合、线程池、死锁、非阻塞、AQS...

JDK、JRE、JVM 三者有什么关系&#xff1f; 答案&#xff1a; JDK&#xff08;全称 Java Development Kit&#xff09;&#xff0c;Java开发工具包&#xff0c;能独立创建、编译、运行程序。 JDK JRE java开发工具&#xff08;javac.exe/java.exe/jar.exe) JRE&#xff08;…

PTA 最小生成树-kruskal

7-92 最小生成树-kruskal 分数 10 全屏浏览题目 作者 任唯 单位 河北农业大学 题目给出一个无向连通图&#xff0c;要求求出其最小生成树的权值。 温馨提示&#xff1a;本题请使用kruskal最小生成树算法。 输入格式: 输出格式: 输出一个整数表示最小生成树的各边的长度之和。…

java实现单链表

链表是java数据结构中一种很基础很常见却也很重要的数据结构&#xff0c;JDK中许多内置jar包基于单链表实现&#xff0c;比如像我们熟悉的linkedList等&#xff0c;为什么要使用链表呢&#xff1f; 我们知道java中很多集合的底层是基于数组实现的&#xff0c;数组有一个很重要…

sql 两表数据合并_多表查询SQL语句

本篇文章中主要讲述以下内容&#xff1a;一、表的加法合并两张表的过程&#xff1a;然后运用sql语句&#xff1a;select 课程号,课程名称 from course union select 课程号,课程名称 from course1以上子句会把两个表中重复数据删除。要想不删除重复的行&#xff0c;则需要在上面…

angularjs增删改查数据_MongoDB数据读写操作(增删改查)总结

《大数据和人工智能交流》头条号向广大初学者新增C 、Java 、Python 、Scala、javascript 等目前流行的计算机、大数据编程语言&#xff0c;希望大家以后关注本头条号更多的内容。一、在执行mongo.exe文件后&#xff0c;进入MongoDB的shell 操作1、创建一个数据库use users2、查…

SpringAop与AspectJ的联系与区别____比较分析 Spring AOP 和 AspectJ 之间的差别

SpringAop与AspectJ的联系与区别 区别 AspectJ AspectJ是一个面向切面的框架&#xff0c;它扩展了Java语言。AspectJ定义了AOP语法&#xff0c;所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。 spring aop Spring提供了四种类型的Aop支持 * 基于经典的…

ssrf漏洞内网渗透_渗透小白看了也能明白的SSRF

什么是SSRF含义服务器端请求伪造&#xff08;SSRF&#xff09;是指攻击者能够从易受攻击的Web应用程序发送精心设计的请求的对其他网站进行攻击。(利用一个可发起网络请求的服务当作跳板来攻击其他服务)攻击者能够利用目标帮助攻击者访问其他想要攻击的目标攻击者要求服务器为他…

高并发之服务降级和服务熔断____服务降级、熔断、限流的区别

高并发之服务降级和服务熔断 服务降级&#xff1a; 服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级&#xff0c;以此环节服务器的压力&#xff0c;以保证核心任务的进行。 同时保证部分甚至大部分任务客户能得到正确的相应。也就是当前的请求处理…

Controller数据导出Excel 详细教程——easypoi-base,easypoi-web,easypoi-annotation

Controller获取数据导出Excel&#xff0c;详细教程 1&#xff1a;导入对应依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.1.3</version></dependency><dependency&g…

01 - java 开始

Java 的优势 java适合做web后台 java配置环境的时候 java的安装目录&#xff1a;JAVA_HOMEjdk的安装目录&#xff1a;%JAVA_HOME%\bin java的执行机制 先将.java的文件用 javac 编译成 .class的字节码文件再将这些 .class 的字节码文件用 java 执行&#xff08;执行的是文…

15 - java 继承

java继承 This > Son Super > Father Object 类是所有类的顶级父类 只要创建一个类默认都会继承这个顶级父类 – Object 在子类继承父类里面 构造器必须先构造一个父类&#xff08;先有爸爸&#xff0c;才有儿子&#xff09;&#xff0c;必须首先构造父类&#xff0…

easyPOI基本用法详解

文章目录easyPOI基本用法1.Excel文件的简单导入和导出1.1准备工作1.2导入1.3导出1.4图片的导出1.5图片的导入1.6excel模板导出文件1.7excel转html2.Word文件导出2.1使用word模板导出2.2使用word模板导出多页3.excel导入时验证3.1环境准备3.2实战演练3.3注意事项easyPOI基本用法…

16 - java 类加载顺序

类的加载顺序 类对象、静态变量是存在元空间的方法区&#xff0c;实例对象是new出来的&#xff0c;放在堆里面的 一个类加载到内存的完整过程 加载父类 --> 加载子类 --> 构造父类 --> 构造子类 class文件要从磁盘加载到内存形成对象 内存靠地址去取寻址 – 随机存…

360全景倒车影像怎么看_别克关怀-后视镜和倒车影像 倒车时到底看哪个

很多人在考驾照的时候&#xff0c;倒车倒的都很熟练&#xff0c;但是一上路就不行了。一方面&#xff0c;这是因为道路上的状况多变&#xff0c;时常有行人经过&#xff0c;另一方面&#xff0c;上路之后&#xff0c;遇见的停车位千奇百怪&#xff0c;什么样子的都有&#xff0…

17 - 引用类型比较内容

引用数据类型比较 引用数据类型直接比较一定是 false --> 比较的是它俩的地址 Animail a1 new Animal(); Animail a2 new Animal(); System.out.println(a1 a2); //false特殊的 String String s1 "a"; String s2 "a"; System.out.println(s1 s…

springboot使用jxls导出excel___(万能通用模板)--- SpringBoot导入、导出Excel文件___SpringBoot整合EasyExcel模板导出Excel

springboot使用jxls导出excel 实现思路&#xff1a; 首先在springBoot(或者SpringCloud)项目的默认templates目录放入提前定义好的Excel模板&#xff0c;然后在具体的导出接口业务代码里通过IO流加载到这个Excel模板文件&#xff0c;读取指定的工作薄(也就是excel左下角的Shee…

idea(mac) 使用收集

其实 idea 后面的 webstorm phpstorm pycharm… 都大同小异 idea 使用积累1. 设置代码区字体大小2. command​ 滚轮改变字体大小3. 去掉代码区中间的白线4. 查看项目配置5. 自动删除类中无用包、自动导入包6. 显示行号和方法分隔符7. 提示的时候忽略大小写8. 统一显示 utf - 8…

epp是什么意思_什么是1K/2K/3K注塑?

ABC小词条的出现是因为公众号后台大家留言提问的名词很多&#xff0c;我们每周挑一个成本分析相关的小知识点&#xff0c;可能是工艺&#xff0c;成本方法&#xff0c;产品方面等话题&#xff0c;来做一个简短的ABC解释&#xff0c;如有错误欢迎指出。文末会提出一个问题&#…