@Transation在什么情况下会失效(上)| 小册免费学

353 阅读8分钟

Spring的事务管理机制分为两种:

编程式事务(代码中手动开启事务)和声明式事务(使用注解开启事务)

编程式事务:

代码中手动管理事务的回滚、提交,代码侵入性比较强。 try { //TODO something transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw new InvoiceApplyException("异常失败"); }

声明式事务:

基于AOP切面的,它将具体业务代码和事务处理代码部分解耦,代码侵入性很低(使用cglib动态代理),所以实际开发中用声明式事务用的比较多,声明式事务有两种实现方式: 1.基于TX和AOP的xml配置文件方式 2.基于Transsactional注解

@Trancational介绍

作用位置:接口、类、类方法 1.作用于接口:(不推荐) 在接口上声明后,而Spring AOP配置的代理方式又是Cglib代理的话,会造成@Transational失效。 2.作用于类: 类中的所有public方法都会配置成相同的事务属性信息。 3.作用于类方法: 当类与类方法都配置了@Transational属性的话,方法上的事务会覆盖类上的方法。

@Trancational属性

propagation属性:(代表事务的传播行为,默认值为Propagation.REQUIRED) 》Propagation.REQUIRED:如果当前存在事务则加入该事务,如果不存在事务,则创建一个新的事务。(也就是说A方法和B方法都添加了注解,在默认的REQUIRED属性下,A方法内部调用B方法,并把B方法加入到A方法中,合并为一个事务) 》Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。 》Propagation.MANDATORY:如果当前存在事务,则加入事务;如果当前不存在事务,则抛出异常 》Propagation.REQUIRES_NEW:重新创建一个新的事务,如果存在事务,暂停当前的事务。(当类中A中的a方法用默认Propagation.REQQUIRED模式,类B中的b方法采用Propagation.REQQUIRED_NEW模式,然后在a方法中调用b方法操作数据库,然而a方法抛出异常,b方法正常执行,则b不进行回滚,因为Propagation.REQUIRED_NEW会暂停a方法) 》Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。 》Propagation.NEVER:以非事务的方式运行,如果存在事务,则抛出异常 》Propagation.NESTED:和Propagation.REQUIRED效果一样 isolation属性:事务的隔离级别,默认值为Isolation.DEFFAULT 》Isolation.DEFAULT:使用底层数据库默认的隔离级别 》Isolation.READ_UNCOMMITED 》Isolation.READ_COMMITTED 》Isolation.REPEATABLE_READ 》Isolation.SERIALIZABLE timeout属性: 事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 readOnly:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 rollbackFor 属性 rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。 noRollbackFor属性 noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

@Transational失效场景

1.@Transational作用于非public方法上 AOP代理时,会通过事务拦截器(TransactionInterceptor)在目标方法执行前后进行拦截,代理类中的生效方法会间接调用computeTransactionAttribute方法读取事务注解的配置信息 ,会检查该注解的目标方法是否为public,不是则不会增强事务处理信息。也就是代理类并未加强非public的方法

2.@Transational使用的Papagation传播行为不对 如使用了SUPPORT、NOT_SUPPORT、NERVER等属性需要注意

3.@Transational使用的rollbackFor设置错误 可以指定事务回滚的异常类型,Spring默认的抛出的是未检查的异常,继承于RuntimeExcetion或者Error的类(子类会回滚,Spring内部会使用getDepth递归查找子类),其他异常不回滚。如果希望Spring处理其他类型的异常,可以设置rollbackFor属性。

4.一个没有事务注解的方法调用有事务注解的方法,导致@Transational失效 因为AOP的代理造成的,没有事务注解的方法,方法没有增强,且在当前方法中调用有事务注解的方法时,调用的为当前类对象的方法,并不是代理类的增强方法。

5.异常被catch吃掉了 有事务注解的业务A类a方法调用有事务注解B类b方法,b方法抛出异常,a方法catch(事务注解默认属性) 假如业务B中的b方法有事务注解且抛出异常,业务B类认为要回滚,而a方法catch捕获处理,则ServiceA类任务应该正常提交。就会造成前后不一致。 spring的事务在方法之前开启的,事务回滚还是提交,取决于代码是否抛出Runtime异常,或者catch到后是否抛出Runtime异常,如果没有的话,事务不会回滚。 业务方法一般不需要try catch,如果非要catch记得抛出Runtime异常,或者rollback属性设置为Exception.class并将异常抛出;

6.数据库引擎不支持事务 SpringBoot自动装配默认开启事务管理,无需@EnableTransactionManagement。 @Transational开发中最常用的属性的由两个:propagation、rollbackFor两个属性值的默认值: propagation的默认值为REQUIRED,方法A调用方法B,如果A有事务则加入A中事务。事务具体属性取决于在哪个方法中触发的。

rollbackFor:默认值为RuntimeExcepion和Error及其继承子类。

高频导致@Transational失效情景

1.同一个类中没有事务注解的方法调用有事务注解的方法,导致@Transational失效

2.异常被catch捕获了

对于第2种情况,比较好的处理办法是尽量不在Service层处理异常try catch,而是习惯抛出业务异常,让@RestControllerAdvise统一捕获并返回前端。

同一个类中没有事务注解的方法调用有事务注解的方法,导致@Transational失效,解决方法:

1.给没加事务注解的方法也加上事务注解。 同一个类中非事务方法调用事务方法失效的原因在于:非事务方法调用事务方法时调用的为this.xxx方法,而这里的this为普通为加强的方法,非加强了事务的代理对象。 解决方案:所以在非事务方法调用事务方法时,通过加强了事务的代理对象去调用即可。

2.从ApplicationContext中获取代理对象(事务加强后的对象)

3.通过@Autowired注入自身(得到的对象为加强了事务的代理对象)

4.引入aop依赖,使用@EnableAsceptJAutoProxy(exposeProxy=false),最后通过AopContext.currentProxy()获取到代理对象。

关于代理对象与this

同一个类中没有事务注解的方法调用有事务注解的方法失效。怎么使有事务注解的方法生效呢? 第一种方法在没有事务的方法上加事务注解,其实是让没有事务的方法加上事务,然后让被调用的事务注解方法加入到调用的事务方法中。其实并没有解决被调用的事务方法失效的问题,内部还是调用this.XXX方法,为未加强的普通对象的方法。当这个方法报错时会沿着方法链向上抛,直到传到有事务加强的方法时回滚。

Why?this不是代理加强对象呢? 动态代理的原理是,如InvocationHandler的invoke()方法中会使用target目标对象对象原方法,所以当target调用目标方法的里面时,还是目标对象的this。

Spring通过注解自动注入对象时,会将代理对象注入进去,当调用代理对象的方法时,最终会调用目标对象的方法。

事务是否生效基于以下两点:

1.是否有给方法做增强的事务注解或xml配置

2.调用时是否使用的是增强了事务处理的方法

代理对象为什么加了@Transational注解之后就会触发事务?

在AOP源码中一个个增加的方法被会被包装成一个个拦截器,放在拦截器链中。

1.当代理对象执行每个方法时,最终会导向CglibAopProxy的内部类DynamicAdvisedInterceptor的intercept方法,而在这个方法中会判断当前方法有没有要执行的拦截器链。

在第二点的判断条件中如果链为空且方法为public时,执行代理方法对象的invoke方法。所以注解放在private私有方法上也会失效。 2.当public方法加了事务注解,事务处理的代码就会被加入到拦截链中,最后会在事务方法前后调用。

特别要注意,任何Java代码层面的事务控制其实还是依赖于setAutoCommit(false),也就是先关闭默认提交,此时MySQL底层就会通过日志把一连串操作先记录起来,最后一起提交。如果中间失败了,仍可根据日志回滚。具体实现细节可以去查阅MySQL事务相关资料。 在事务切面支持类中第二行通过目标方法和目标对象后去事务注解的属性然后通过属性,在开启事务时判断是否需要事务。rollbackFor,pagation等属性。

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情