事务未生效可能造成严重的数据不一致性问题,因而保证事务生效至关重要。Spring事务是通过Spring aop实现的,所以不生效的本质问题是spring aop没生效,或者说没有代理成功,所以有必要了解下spring aop。
spring事务不生效场景
- 非public修饰方法private修饰@Transactionalprivate void save()调用save方法,事务不生效
- protected修饰@Transactionalprotected void save()调用save方法,事务不生效
- final修饰方法@Transactionalpublic final void save()调用save方法,事务不生效
- 非事务方法调用内部事务方法public void save(){insert()}@Transactionalpublic void insert()此时调用save()方法,insert()上的事务不生效
public void save(){insert()}@Transactionalpublic void insert()此时调用save()方法,insert()上的事务不生效,save()方法正常调用时(非内部方法调用)事务生效
Spring aop的简单介绍
spring aop支持动态代理(运行时代理)和静态代理(编译期织入)
-
基本概念
target对象目标对象,即想要代理的目标。
- proxy代理对象,访问目标对象的方法,需要通过代理对象,无法直接访问目标对象。代理对象可能允许,最终调用目标对象方法,也可能不允许。
静态代理(Aspectj)1.该实现需要使用特殊编译器,一般不使用,spring只是提供相应的整合实现。2.使用动态代理已经可以完成80%以上的需求了。3.本文也是基于动态代理的情况下讨论事务不生效情况的。4.因此在此不展开详细讨论。
动态代理(CGLIB&JDK)
- JDK动态代理1.JDK动态代理通过实现相同接口实现代理(Proxy)2.spring管理的Bean中是对应的是Proxy3.Proxy对象中包含target对象4.目标方法可以执行时,总是调用target的原生方法5.事务aop默认publicMethodsOnly(该配置未提供直接修改-AnnotationTransactionAttributeSource)
Spring 事务不生效情况(即aop不生效情况),原因解析
- 无法代理方法使用非public修饰(protected、private等等)@Transactionalprivate void save()或
@Transactionalprotected void save()spring 事务aop默认publicMethodsOnly,即只有public修饰的方法,会被代理,private和protected修饰的方法save()无法被代理,事务无效。 - 方法使用final修饰@Transactionalpublic final void save()CGLIB动态代理通过生成子类的方式代理,final方法无法重写
- 类使用final修饰@Transactionalpublic final class UserServiceCGLIB动态代理通过生成子类的方式代理,final类无法生成子类
- 非事务方法调用内部事务方法public void save(){insert()}@Transactionalpublic void insert()该情况隐含使用this关键字,因此被代理对象的内部事务方法insert()直接被调用,而不经过代理对象;内部方法insert()上的事务无效。
public void save(){insert()}@Transactionalpublic void insert()该情况隐含使用this关键字,因此被代理对象的内部事务方法insert()直接被调用,而不经过代理对象;内部方法insert()上的事务无效,但是事务方法save()正确调用时(通过代理对象调用),save()方法的事务生效。
调试辅助
当无法通过代码分析确定是否有事务(aop是否代理成功)时,可以在开发环境调试确认
- 事务AOP在配置了expose-proxy=true时,调用静态方法TransactionAspectSupport.currentTransactionStatus()可以获取当前事务信息;该信息存储在ThreadLocal类型的线程变量;不存在时会抛异常,所以使用时需要try-catch。
- 其他AOP在配置了expose-proxy=true时,调用静态方法AopContext.currentProxy()可以获取当前代理对象;该对象存储在ThreadLocal类型的线程变量;不存在时会抛异常,所以使用时需要try-catch。