文章目录

  • 一、事务(基于AOP)
  • 二、@Transactional介绍
  • 三、@Transactional失效场景

说明:当我准备写我知道的那几个场景时,我发现有人比我写的更好,关键是好得多,于是我就用了这位博主的文章,由于不是一个平台我就直接将有些内容复制加以修改,并附上自己的理解,感谢这位博主!他地址: https://www.cnblogs.com/frankyou/p/12691463.html

前置知识:
1、Spring的事务支持是由AOP(面向切面编程)这一思想做支撑,然而面向切面编程底层的落地实现是Spring工厂的动态代理,而Spring底层又对JDK或cgLib这两种实现动态代理的方式做了封装,Spring可以利用AOP思想实现事务、日志、性能监控、接口限流

代理的作用或好处:
作用:为原始对象增加额外功能;
好处:解耦合、便于原始对象的维护。

2、什么是代理:代理就相当于租房中介,他为房东提供发放广告和提供看房的额外功能

3、什么是代理类:代理类就是为原始类添加额外功能的类,这个类是由JVM通过动态字节码技术生成的。

4、什么是动态字节码技术:是利用类加载器动态的在JVM中,由原始类和额外功能的字节码自动生成代理类的字节码,从而使用类加载器在堆空间中创建Class对象。

5、代理实现的三要素:①提供原始对象(为其增加额外功能);②额外功能;③要求代理类要与原始类拥有相同的方法(可以是实现其接口,也可以继承)

6、JDK提供动态代理:是利用Proxy类中的一个静态方法newProxyInstance创建代理类对象

    // 1、创建原始对象UserServiceImpl userService = new UserServiceImpl();// 2、提供额外功能InvocationHandler handler = new InvocationHandler() {/** @param proxy 代理实例,一般不会用到*   @param method 原始方法*   @param args 原始方法的参数*   @return 原始对象返回值   */@Overridepublic Object invoke(Object proxy, Method method, Object[] args) {System.out.println("在原始方法执行之前执行的逻辑 -- 额外功能"); // 2.1 这其实就是Spring提供的前置通知-MethodBeforeAdviceObject invoke = null;try {invoke = method.invoke(userService, args); // 调用原始方法,执行原始方法逻辑} catch (Exception e) {System.out.println("在原始方法执行报错后执行的逻辑 -- 额外功能"); // 2.2 这其实就是Spring提供的异常通知-ThrowsAdvice}System.out.println("在原始方法执行之后执行的逻辑 -- 额外功能"); // 2.3 这其实就是Spring提供的后置通知-AfterReturningAdvicereturn invoke; //}};// 创建代理对象UserServiceImpl userServiceProxy = (UserServiceImpl)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),userServiceImpl.getClass().getInterfaces(),handler);userServiceProxy.save(new User()); // 使用代理对象调用原始类中的方法,在执行此方法时会按照代理对象的逻辑执行额外功能

7、newProxyInstance方法的三个参数说明:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException

loader类加载器:利用该类加载器去生成字节码,创建Class对象;
interfaces接口集合:这就是为了使生成的代理类和原始类的方法一致;
InvocationHandler额外功能接口的接口:额外功能的逻辑
利用这三个参数就可以创建代理类对象,cgLib的实现与以上步骤高度吻合,区别就是cgLib是使用继承原始类的方式去让代理类与原始类方法相同的。

Spring对JDK动态代理的封装:

Spring就是利用JDK的动态代理来实现AOP思想的,它封装之后提供最重要的接口叫MethodInterceptor.

public class proxyTest implements MethodInterceptor {/*** @param methodInvocation 原始对象* @return 原始对象返回值* @throws Throwable 异常处理*/@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("之前");Object proceed = methodInvocation.proceed(); // 调用原始方法,执行原始方法逻辑System.out.println("之后");return proceed;}
}

你会发现,Spring提供的这个MethodInterceptor 接口的实现方法invoke和代理newProxyInstance方法高度吻合,Spring就是把这个方法给封装了,但是最底层还是完全一样的。

那么,现在我们再来看Spring的事务。

一、事务(基于AOP)

1、事务: 事务是用来保证业务的完整性的一种数据库机制,在数据库层面它是一组sql的集合,要保证这组sql集合要么同时发生,要么同时失败,以此来保证数据的安全和一致

2、事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。

(1)编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,其中也会用到try-catch。
缺点:代码侵入性比较强,如下示例:

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

其实这个方式,我觉得Spring封装的力度并不是很大,只是利用Spring为我们sqlSession以及创建了DAO对象。还没有用到Spring提供的事务封装(事务封装:封装动态代理的切入点和连接connection),还是由我们手动调用connection连接的commitrollback

(2)声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。
声明式事务也有两种实现方式,一是基于tx和AOP的xml配置文件方式,二是基于@Transactional注解。

    <!--事务 - - 额外功能--><bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--组装切面 - - 组装 切入点 + 额外功能(也就是让Spring知道哪一个包、类、方法要是哟个和哪一个额外功能,此时的额外功能是事务的支持)  --><tx:annotation-driven transaction-manager="dataSourceTransactionManager"/><aop:aspectj-autoproxy proxy-target-class="false"/>

二、@Transactional介绍

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

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

(1)作用于类:当把@Transactional 注解放在类上时,表示所有该类的 public 方法 都配置相同的事务属性信息。
(2)作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会 覆盖类的事务配置信息。
(3)作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

2、@Transactional注解有哪些属性?

@RestController
publicclass MybatisPlusController {@Autowiredprivate CityInfoDictMapper cityInfoDictMapper;@Transactional( isolation = Isolation.DEFAULT, propagation = Propagation.SUPPORTS, timeout = 2, readOnly = true, rollbackFor = RuntimeException.class)@GetMapping("/test")public String test() throws Exception {return "";}
}

(1)isolation 属性

isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。这个事务的隔离级别可以理解成Mysql中的事务隔离级别。

TransactionDefinition.ISOLATION_DEFAULT :使用数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ(不可重复读)隔离级别, Oracle 默认采用的 READ_COMMITTED(读已提交)隔离级别.

TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别(是因为这样的效率很低,并发性大大的降低,而且很多时候也没有必要去解决幻读)。

(2)propagation属性

propagation 代表事务的传播行为**,默认值为 Propagation.REQUIRED,一共是7种前面三种得掌握。其他的属性信息如下:**

Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )。注意:这适用于增删改.

Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。(有事务就继续执行当前事务,要是开启事务也会去创建事务,而是继续以没有事务的方式去执行任务。)注意:这适用于查询.

Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,挂起当前的事务。( A方法有事务,去调用没有事务的B方法,那么B方法就会去创建一个新的事务,然后将A的事务挂起,去执行B的事务。

④Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
⑤Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
⑥Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
⑦Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

(3)timeout 属性

timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
值得注意的是:这个属性一般都不会去设置。原因是在真实的环境中,由于网络环境和其他不确定性因素在里面,很难去确定一个时间段内哪一个事务是要超时的。

(4)readOnly 属性

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

(5)rollbackFor 属性

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

(6)noRollbackFor属性

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

三、@Transactional失效场景

接下来我们结合具体的代码分析一下哪些场景下,@Transactional 注解会失效。

1、没有将使用该注解的类交给Spring管理,也就是该对象不是bean对象。
在添加@Transactional 的注解的类交给Spring管理,要使用像@service类似的注解。

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

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

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

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

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

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

小结:其实简单理解就是,比如通过cgLib的方式去生成代理类对象,它的底层原理是去继承原始类(或者说目标类),然而我们都知道,要被继承的方法是不能用非public修饰的,假如原始类中的目标方法被private修饰,那么该方法就不能被继承下去,spring使用动态代理就不能为其生成代理对象,又因为 @Transactional 注解标注的这个方法必须被代理类对象调用才可以生效以此被代理。

3、@Transactional 注解属性 propagation 设置错误

这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

(1)TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

(2)TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

(3)TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

4、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring检查到是 unchecked 异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性,如果未指定 rollbackFor 属性又发生了其他异常,则事务不会回滚。

// 自定义的异常,都会进行数据回滚(...表示还可以指定其他异常)
@Transactional(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 失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它有一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),并且方法A没有声明注解事务,而B方法声明了。此时外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

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

//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("2");/*** B 插入字段为 3的数据*/this.insertB();/*** A 插入字段为 2的数据*/int insert = cityInfoDictMapper.insert(cityInfoDict);return insert;
}@Transactional()
public Integer insertB() throws Exception {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("3");cityInfoDict.setParentCityId(3);return cityInfoDictMapper.insert(cityInfoDict);
}

此点可以用传播属性来规避。

6、异常在方法内部使用 catch 捕捉了,导致 @Transactional 失效

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

@Transactional
private Integer A() throws Exception {int insert = 0;try {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("2");cityInfoDict.setParentCityId(2);/*** A 插入字段为 2的数据*/insert = cityInfoDictMapper.insert(cityInfoDict);/*** B 插入字段为 3的数据*/b.insertB();} catch (Exception e) {e.printStackTrace();}
}

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

答案:不能!

会抛出异常:

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

因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出RuntimeException 并在你的业务方法中没有catch到的话,事务会回滚。

在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候 try catch反倒会画蛇添足。

7、数据库引擎不支持事务

这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

总结
@Transactional 注解的看似简单易用,但如果对它的用法一知半解,还是会踩到很多坑的。

聊一聊Spring统一处理事务 并代码演示案例(整合Mybatis)

有用点个关注,手留余香!

聊一聊Spring中@Transactional注解及其失效的七种场景相关推荐

  1. mock 中使用@Transactional注解测试失效问题

    mock 中使用@Transactional注解测试失效问题,只需要引入这个就好了 <dependency><groupId>com.h2database</groupI ...

  2. 一口气说出 6 种 @Transactional 注解的失效场景

    一.事务 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种. 编程式事务:是指在代码中手动的管理事务的提交.回滚等操作,代码侵入性比较强, ...

  3. @getmapping注解的作用_一口气说出6种,@Transactional注解的失效场景

    作者:程序员内点事 引言 昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败.所以今天简单的和大家分享一下@Transactiona ...

  4. 面试官让我说出2种@Transactional注解的失效场景,我一口气给他说了六种

    引言 @Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功.要么同时失败.使用**@Transactional注解时需要注意许多的细 ...

  5. @Transactional 注解的失效场景

    作者:码农开花 链接:https://zhuanlan.zhihu.com/p/351260443 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 引言 有人咨询了一 ...

  6. @Transactional注解的失效场景

    @Transactional注解的失效场景 引言 @Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功.要么同时失败.使用@Tran ...

  7. Spring的@Transactional注解踩坑

    @Transactional介绍 Spring为开发人员提供了声明式事务的使用方式,即在方法上标记@Transactional注解来开启事务.大家在日常的开发中很多业务代码对数据进行操作的时候一定是希 ...

  8. 声明式事务、Spring 中常用注解、Ajax

    五. 声明式事务 编程式事务: 1.1 由程序员编程事务控制代码. 1.2 OpenSessionInView 编程式事务 声明式事务: 先引入依赖 <dependency><gro ...

  9. spring中的注解和xml配置文件中配置对应总结

    spring中的注解和xml配置文件中配置对应 需要导入的jar spring-context spring-context-support spring-test commons-logging b ...

最新文章

  1. NAT双出口的热备份
  2. 网站设计与开发流程图
  3. DPDK加速I/O虚拟化
  4. Phyton自定义包导入。
  5. flatform installer web 安装php_web安装平台-微软web服务器配置安装工具(Web Platform Installer)5.0 官方最新版-东坡下载...
  6. 实现文件拖放的一种简洁方法
  7. 【李宏毅机器学习】Gradient Descent_1 梯度下降(p5、p6、p7 )学习笔记
  8. Android 驱动(16)---AOSP如何单独build kernel
  9. java三种循环结构的关键字,Java循环结构_常量_关键字
  10. [ER/Studio]进行不同版本数据库结构的合并
  11. 六面美团后,我有一个重要的发现...
  12. 车牌号识别易语言代码
  13. Hive Hsql 常用命令
  14. python可以用于工业机器人编程与操作_非常实用的工业机器人编程语言有哪些?这些编程好用吗?...
  15. vector扩容时以2倍或1.5倍扩容的原因
  16. 第106章 Caché 函数大全 $ZF(-4),$ZF(-5),$ZF(-6) 函数
  17. pandas常用操作
  18. ps导出gif时是html,ps导出gif图步骤图解
  19. 图书销售管理系统的设计与实现
  20. 12-16面向对象之接口和抽象类的应用

热门文章

  1. 微信多开防撤回工具再也不用担心好友撤回消息和登录多个账号了
  2. tomcat配置url跳转_Tomcat
  3. 【python系列】使用mayavi画3d散点图
  4. 移动端js触摸touch详解(附带案例源码)
  5. 圣思园——Java SE Lesson 4
  6. [转] TCP/IP原理、基础以及在Linux上的实现
  7. H3C设备运行状态查询常用命令(建议收藏)
  8. 关于jacoco的学习
  9. iOS release版本
  10. 游戏浅谈1-传奇,跑跑卡丁车