@Transaction注解哪些情况不生效

看一个最简单的CGLIB的例子,感受一下AOP是如何做到的?

/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 上午9:25
 */
public class Target {  
    public String execute() {
        String message = "----------test()----------";
        System.out.println(message);
        return message;
    }
}

/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 上午9:25
 */
public class MyMethodInterceptor  implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(">>>MethodInterceptor start...");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println(">>>MethodInterceptor ending...");
        return "haha";
    }
}

/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 上午9:28
 */
public class CglibProxyTest {

    public Object createProxy(Class targetClass) {
       // 第一步
       Enhancer enhancer = new Enhancer();
       // 第二步
       enhancer.setSuperclass(targetClass);
       // 第三步
       enhancer.setCallback(new MyMethodInterceptor());
       // 第四步
       return enhancer.create();
    }

    public static void main(String rags[]) {
        CglibProxyTest cglibProxyTest = new CglibProxyTest();
        Target proxyTarget = (Target) cglibProxyTest.createProxy(Target.class);
        String res = proxyTarget.execute();
        System.out.println(res);
    }

}

执行后的结果显示

Connected to the target VM, address: '127.0.0.1:55868', transport: 'socket'  
>>>MethodInterceptor start...
----------test()----------
>>>MethodInterceptor ending...
haha  
Disconnected from the target VM, address: '127.0.0.1:55868', transport: 'socket'  

实际上在执行execute()的前后就各自做了自己想要的操作。其实这个就是Spring AOP对简单的一个原型。

@Transaction的工作原理

在Spring中TransactionInterceptorPlatformTransactionManager这两个类是整个事务模块的核心,TransactionInterceptor负责拦截方法执行,进行判断是否需要提交或者回滚事务。PlatformTransactionManager是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。我们重点研究下这两个类的源码。

TransactionInterceptor类中的代码有很多,我简化一下逻辑,方便说明:

//以下代码省略部分内容
public Object invoke(MethodInvocation invocation) throws Throwable {  
//获取事务调用的目标方法
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);  
//执行带事务调用
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);  
}

其实,这里也就是因为用到了动态代理。在事务回滚这个动作的前前后后可以做自己想要的东西。这是一个非常重要的设计思想。如果我们自己要写框架,这个模式可以作为你的第一参考。

基于注解的实现机制

  • 调用注解方法
  • 生成代理对象 - CglibAopProxy 调用内部类的方法DynamicAdvisedInterceptor.intercept()
  • TransactionInterceptor.invoke()拦截器拦截,在目标方法执行之前创建并加入事务
  • AbstractPlatformTransactionManager抽象事务管理器操作数据源DataSource提交或回滚事务

我们看了解一下它是如何仿照最上面的code来写的?

仿照上面Demo的第一步:

public Object getProxy(@Nullable ClassLoader classLoader) {  
        if (logger.isTraceEnabled()) {
            logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
        }

        try {
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class<?> proxySuperClass = rootClass;
            if (ClassUtils.isCglibProxyClass(rootClass)) {
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }

            // Validate the class, writing log messages as necessary.
            validateClassIfNecessary(proxySuperClass, classLoader);

            // 第一点:Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
      // 第二点:setSuperclass
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
            // 第三点:获取Callback
            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            // fixedInterceptorMap only populated at this point, after getCallbacks call above
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
            enhancer.setCallbackTypes(types);

            // Generate the proxy class and create a proxy instance.
            return createProxyClassAndInstance(enhancer, callbacks);
        }
        catch (CodeGenerationException | IllegalArgumentException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                    ": Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (Throwable ex) {
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        }
    }

仿照上面Demo的第三步:

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {  
        // Parameters used for optimization choices...
        boolean exposeProxy = this.advised.isExposeProxy();
        boolean isFrozen = this.advised.isFrozen();
        boolean isStatic = this.advised.getTargetSource().isStatic();

        // Choose an "aop" interceptor (used for AOP calls).
        Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

        // Choose a "straight to target" interceptor. (used for calls that are
        // unadvised but can return this). May be required to expose the proxy.
        Callback targetInterceptor;
        if (exposeProxy) {
            targetInterceptor = (isStatic ?
                    new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                    new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
        }
        else {
            targetInterceptor = (isStatic ?
                    new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                    new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
        }

        // Choose a "direct to target" dispatcher (used for
        // unadvised calls to static targets that cannot return this).
        Callback targetDispatcher = (isStatic ?
                new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

        Callback[] mainCallbacks = new Callback[] {
                aopInterceptor,  // for normal advice
                targetInterceptor,  // invoke target without considering advice, if optimized
                new SerializableNoOp(),  // no override for methods mapped to this
                targetDispatcher, this.advisedDispatcher,
                new EqualsInterceptor(this.advised),
                new HashCodeInterceptor(this.advised)
        };

        Callback[] callbacks;

        // If the target is a static one and the advice chain is frozen,
        // then we can make some optimizations by sending the AOP calls
        // direct to the target using the fixed chain for that method.
        if (isStatic && isFrozen) {
            Method[] methods = rootClass.getMethods();
            Callback[] fixedCallbacks = new Callback[methods.length];
            this.fixedInterceptorMap = new HashMap<>(methods.length);

            // TODO: small memory optimization here (can skip creation for methods with no advice)
            for (int x = 0; x < methods.length; x++) {
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
                fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                        chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
                this.fixedInterceptorMap.put(methods[x].toString(), x);
            }

            // Now copy both the callbacks from mainCallbacks
            // and fixedCallbacks into the callbacks array.
            callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
            System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
            System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
            this.fixedInterceptorOffset = mainCallbacks.length;
        }
        else {
            callbacks = mainCallbacks;
        }
        return callbacks;
    }

仿照上面Demo的第四步:

protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {  
        enhancer.setInterceptDuringConstruction(false);
        enhancer.setCallbacks(callbacks);
        return (this.constructorArgs != null && this.constructorArgTypes != null ?
                enhancer.create(this.constructorArgTypes, this.constructorArgs) :
                enhancer.create());
    }

上面的几步就完成了一个动态代理的流程,就只需要真的发生调用的时候去执行动态代理类了。

哪些场景事务会失效?

  • 1、只对public修饰方法才起作用
  • 2、@Transaction默认检测异常为RuntimeException及其子类 如果有其他异常需要回滚事务的需要自己手动配置,例如:@Transactional(rollbackFor = Exception.class)
  • 3、确保异常没有被try-catch{},catch以后也不会回滚
  • 4、检查下自己的数据库是否支持事务,如mysql的mylsam
  • 5、Springboot项目默认已经支持事务,不用配置;其他类型项目需要在xml中配置是否开启事务
  • 6、如果在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效,因为代理问题

这里说下在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效。如果是在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用,必须将方法放入另外一个类中,并且该类通过Spring注入。

Spring 采用动态代理(AOP)实现对Bean的管理和切片,它为我们的每个class生成一个代理对象,只有在代理对象之间进行调用时,可以触发切面逻辑。

而在同一个类中,方法B调用A,调用的事元对象的方法,而不是通过代理对象,所以spring无法切到这次调用,也就是无法通过注解保证事务性。

参考地址

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

简栈文化服务订阅号