一、代理不生效
Spring中对注解的解析都是基于代理的,如果目标方法无法被Spring代理到,那么他就无法被Spring进行事务管理。
而Spring生成代理的方式有两种:
1.基于接口的JDK动态代理,但是要求目标代理类必须要实现一个接口才能被代理
2.基于目标类子类的CGLIB代理
在Spring-boot2.0之前,如果目标类实现了接口,那么将会使用JDK动态代理,否则使用CGLIB子类的方式生成代理;
在Spring-boot2.0之后,除非在配置文件中指定spring.aop.proxy-target-class的值,否则默认情况下将会使用CGLIB生成代理。
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop",name = "proxy-target-class",havingValue = "true",matchIfMissing = true)
public class Test(){
}
而下列原因会因为代理不生效导致事务管控失败
(1)将@Transactional注解标注在接口上
@Transactional是支持在方法与类上的,因为一旦接口实现类的代理方式是CGLIB,他会通过生成子类的方式对目标类进行代理,就会导致标在接口上的@Transactional注解无法被解析到,从而导致事务失效。
(2)被final、static关键字修饰的类或方法
CGLIB是通过生成目标类的子类的方式进行代理的,而被final、static修饰的类或方法无法被继承。
(3)类方法的内部调用
事务的管理是通过代理执行的方式生效的,如果是方法内部的调用,将不会走代理逻辑,也就无法调用到@Transacational注解了。
解决方法有三个:
1.新建Service将方法迁移过去(不建议)
2.在当前类中注入自己,通过注入的service调用本类方法
3.通过AopContext.currentProxy()获取代理对象直接调用方法
(4)当前类没有被Spring管理
二、框架或底层不支持的功能
这类的失效场景主要聚焦在框架本身在解析@Transactional时的内部支持,如果使用的场景本身就是框架不支持的,那么事务也是无法生效的。
(1)非public修饰的方法
@Transactional底层不支持非public修饰的方法的事务管理。
(2)多线程调用
TransactionInfo事务信息是和线程绑定的,即当前线程中无法调用其他线程中的事务信息;
两个线程在不同的Spring事务中,本质上会导致他们在Mysql中存在不同的事务中。
(3)数据库本身不支持事务
比如MySql中的Myisam存储引擎是不支持事务的,只有innodb才支持,但是这个问题出现的概率较小,因为在Mysql5之后默认已经是innodb存储引擎了。
(4)未开启事务
这个问题只会在MVC项目中存在,因为Springboot项目已经默认开启了事务管理,
而如果想要是MVC启用事务,需要在applicationContext.xml文件中,手动配值相关参数。
三、错误使用@Transactional
(1)错误的传播机制
不支持事务的传播机制有:
PROPAGATION_SUPPORTS,
PROPAGATION_NOT_SUPPORTED,
PROPAGATION_NEVER
(2)rollbackFor回滚异常设置错误
默认状态下为RunTimeException,即只会回滚运行时异常;
我们可以指定rollbackFor = Exception.class进行全异常捕获。
(3)异常被内部try catch
@Transactional(rollbackFor = Exception.class)
public void test(){
try{
sout(1/0);
}catch(Exception e ){
}
因为方法内虽然报错了,但是手动进行了try catch,catch中并没有进行对应的异常抛出,就会导致事务不会回滚。
(4)嵌套事务
通常当一个方法回滚时,我们希望让调用方法和数据库操作方法同时回滚,但是有时也只希望回滚数据库操作方法而不影响主方法的运行:
方法1:直接在数据库操作方法内用try catch包住整个方法
方法2:在数据库操作方法中使用Propagation.REQUIRES_NEW传播机制
四、总结
全文END ʕ•ᴥ•ʔ