整理spring事务失效的场景(源码解析)

Spring事务管理方式,我们大部分都是使用声明式来实现,即贴@Transacational注解。但是在我们使用的过程中,会因为使用不当而导致事务失效的问题。下面就罗列出事务失效的常见使用场景,并加以讲解。

场景1:spring的事务注解@Transactional只能放在public非final修饰的方法上才起作用,如果放在其他非public(private,protected)方法上,事务不起作用。

在应用系统调用声明了 @Transactional 的目标方法时,Spring 默认使用 AOP 代理,而Spring AOP实现方式有两种:jdk动态代理实现和cglib动态代理实现,但是无论使用jdk动态代理还是cglib动态代理,@Transactional也只能放在public或public final修饰的方法上才起作用。
下面是从源码的角度来看其中的原因。

jdk动态代理实现

目前,我们大部分业务代码都是写在Service层,即创建一个Service接口,然后创建ServiceImpl类,且该类实现Service接口,再在实现类的对应实现方法上写相关业务逻辑。
这种方式,如果我们在ServiceImpl类或方法上贴上@Transactional 注解,实际上底层是使用了jdk动态代理,会动态的生成一个Service代理对象。

类似这样:

/**
 * @author: Longer
 */
public interface PersonService {
    
    void eat();
}

/**
 * @author: Longer
 */
@Service
public class PersonServiceImpl implements PersonService {

    @Transactional
    @Override
    public void eat() {

    }
}

源码解析
newProxyInstance类是jdk动态生成代理对象的时候需要调用的类。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 首先从缓存查找是否有代理类,没有就生成一个
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * 通过InvocationHandler调用目标类的构造函数
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果构造函数不是public修饰,修改
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

其中查找Proxy类的源码如下:

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //长度检查
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
         
        //调用了下面的WeakCache<K, P, V>.get(K key, P parameter)方法,loader作为key,interfaces作为parameter参数
        //定义如下:proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory())
        return proxyClassCache.get(loader, interfaces);
    }


   //首先当前key(也就是上面的ClassLoader)已经加载存在,就直接从缓存中返回
   //如果不存在,就会通过ProxyClassFactory来创建代理对象
   public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();
         //根据key的hash值和一个ReferenceQueue来构造
        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // 从map中取出cacheKey的值
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier可能是Factory或者CacheValue<V>
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // 缓存中没有supplier,同时supplier中没有
            // 懒加载的方式创建一个Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // 安装 Factory
                    supplier = factory;
                }
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

再看上面提到的ProxyClassFactory类

//类定义
 private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>{

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * 校验当前类加载器ClassLoader解析到的名称和定义的名称是否相同 
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * 校验是否是接口类型,这也就是为什么JDK动态代理只能基于接口
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * 防重
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

           // 代理对象的目录
            String proxyPkg = null;     
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            .....

            /*
             * 生成指定Proxy代理对象的字节码
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                //调用的native方法
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * 生成的代理类有bug
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }

}

从ProxyClassFactory类,我们可以看到,该类是会去校验目标类是否是接口类型。这也就是为什么jdk动态代理只能基于接口实现。

JDK动态代理生成的代理class类名实际上是这样的:public final class $Proxy0 extends Proxy implements PersonService{}(PersonService是被代理接口)
继承了Proxy 和实现了目标接口类PersonService。因为Java不允许多重继承,这就限制了:使用JDK代理不能是普通类或者抽象类,只能是接口类型。
由于接口定义的方法是public的,java要求实现类所实现接口的方法必须是public的(不能是protected,private等),同时不能使用static的修饰符。所以,可以实施接口动态代理的方法只能是使用“public”或“public final”修饰符的方法,其它方法不可能被动态代理,相应的也就不能实施AOP增强,也即不能进行Spring事务增强

cglib动态代理实现

对于普通@Service注解的类(未实现接口)并通过 @Autowired直接注入类的方式,是通过cglib动态代理实现的。

类似这样:

/**
 * @author: Longer
 */
@Service
public class PersonServiceImpl{

    @Transactional
    public void eat() {

    }
}
public class PersonController{
    @Autowired
    private PersonServiceImpl personService;
    @PostMapping(value = "/eat")
    public Result eat() {
      personService.eat();
      return Result.ok();
    }
}

cglib动态代理生成的代理class类名实际上是这样的:

public class Student$$EnhancerByCGLIB$$92f3e3f6 extends Student implements Factory(){}

Student 是被代理类。可以看到代理类是继承了被代理类Student ,这就是与jdk动态代理的区别。jdk代理类是实现被代理接口。
由于cglib是继承代理类,而Java继承由于使用final,static,private修饰符的方法都不能被子类复写,所以这些方法将不能被实施的AOP增强,即不会生成cglib代理对象。所以事务是不生效的。
结论:
cglib字节码动态代理的方案是通过扩展被增强类,动态创建子类的方式进行AOP增强植入的,由于使用final,static,private修饰符的方法都不能被子类复写,所以这些方法将不能被实施的AOP增强。即除了public的非final的实例方法,其他方法均无效。

场景2:方法自调用

目标类直接调用该类的其他标注了@Transactional 的方法(相当于调用了this.对象方法),事务不会起作用。事务不起作用其根本原因就是未通过代理调用,因为事务是在代理中处理的,没通过代理,也就不会有事务的处理。
类似下面的写法,事务是不生效:

/**
 * @author: Longer
 */
@RestController
@RequestMapping("/person")
public class PersonController {
    private PersonServiceImpl personServiceImpl;
    @GetMapping(value = "/eat")
    public Result<?> eat() {
        personServiceImpl.eat();
        return Result.ok();
    }
}

/**
 * @author: Longer
 */
@Service
public class PersonServiceImpl{

    public void eat() {
        run();
    }
    @Transactional
    public void run(){

    }
}

场景3:事务方法抛出非RuntimeException异常,事务不回滚

原因:Spring 默认只为 RuntimeException 异常回滚事务,如果方法往外抛出 checked exception,该方法虽然不会再执行后续操作,但仍会提交已执行的数据操作。这样可能使得只有部分数据提交,造成数据不一致。
比如下面的代码,不会回滚:

 @Transactional
    public void eat() throws IOException {
        Person person = new Person();
        person .setId(1);
        this.save(person);
        throw new IOException("testCheckedTran");
    }

代码不回滚的原因是在插入数据库之后,抛出了IOException,这个异常不属于RuntimeException ,所以不会回滚。

解决办法:自定义回滚策略,可使用@Transactional 的 noRollbackFor,noRollbackForClassName,rollbackFor,rollbackForClassName 属性。如使用:@Transactional(rollbackFor = Exception.class)或@Transactional(rollbackFor = Throwable.class)

场景4:方法部分代码try...catch住,而catch语句块没有往外跑出回滚异常,事务也不会回滚。

在业务代码上,一般不try...catch住,如果要捕获异常的话,需要在catch里跑出回滚异常,否则事务不会回滚。

场景5:数据库存储引擎不支持事务

如:使用mysql作为数据库的话,如果存储引擎不是INNODB,而是MyISAM的话,则事务不生效。

场景6:如果采用spring+spring mvc,则context:component-scan重复扫描问题可能会引起事务失败

如果spring和mvc的配置文件中都扫描了service层,那么事务就会失效。
原因:因为按照spring配置文件的加载顺序来讲,先加载springmvc配置文件,再加载spring配置文件,我们的事物一般都在srping配置文件中进行配置,如果此时在加载srpingMVC配置文件的时候,把servlce也给注册了,但是此时事物还没加载,也就导致后面的事物无法成功注入到service中。所以把对service的扫描放在spring配置文件中或是其他配置文件中。

场景7: 多个事务管理器

当一个应用存在多个事务管理器时,如果不指定事务管理器,@Transactional 会按照事务管理器在配置文件中的初始化顺序使用其中一个。
如果存在多个数据源 datasource1 和 datasource2,假设默认使用 datasource1 的事务管理器,当对 datasource2 进行数据操作时就处于非事务环境。
解决办法是,可以通过@Transactional 的 value 属性指定一个事务管理器。在使用多个事务管理器的情况下,事务不生效的原因在本系列后续文章中会有分析

参考文献链接:
Spring事务Transactional和动态代理-事务失效的场景
jdk动态代理
cglib动态代理