@What does the Transaction annotation not work?

Keywords: Programming Spring socket Database MySQL

Take a look at the simplest example of CGLIB, and feel how AOP is achieved?

/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 9:25 am
 */
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 am
 */
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 am
 */
public class CglibProxyTest {

    public Object createProxy(Class targetClass) {
       // The first step
       Enhancer enhancer = new Enhancer();
       // Step two
       enhancer.setSuperclass(targetClass);
       // Step 3
       enhancer.setCallback(new MyMethodInterceptor());
       // Step 4
       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);
    }

}

Result display after execution

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'

In fact, before and after executing execute(), they do what they want. In fact, this is a prototype of Spring AOP for simplicity.

@How Transaction works

In Spring, two classes, TransactionInterceptor and PlatformTransactionManager, are the core of the whole transaction module. TransactionInterceptor is responsible for intercepting the execution of methods and judging whether the transaction needs to be committed or rolled back. Platform transaction manager is the transaction management interface in Spring, which really defines how transactions are rolled back and committed. We focus on the source code of these two classes.

There are a lot of codes in the TransactionInterceptor class. Let me simplify the logic to facilitate the description:

//The following code omits some content
public Object invoke(MethodInvocation invocation) throws Throwable {
//Gets the target method of the transaction call
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
//Execute call with transaction
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

In fact, this is because of the use of dynamic agents. Before and after the transaction rollback, you can do what you want. This is a very important design idea. If we want to write our own framework, this pattern can be your first reference.

Annotation based implementation mechanism

  • Call annotation method
  • Generate proxy object - CglibAopProxy calls dynam of inner class icAdvisedInterceptor.intercept ()
  • Tr ansactionInterceptor.invoke () interceptor, create and join transaction before target method execution
  • AbstractPlatformTransactionManager Abstract transaction manager action data sourcedatasource commit or rollback transaction

Let's see how it's modeled on the top code?

Follow the first step of Demo above:

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);

			// First point: Configure CGLIB Enhancer
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
      // Second point: setSuperclass
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
			// Third, get the 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);
		}
	}

Follow the third step of Demo above:

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;
	}

Follow the fourth step of Demo above:

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());
	}

In the above steps, a dynamic agent process is completed, and only the dynamic agent class needs to be executed when the call actually occurs.

What scenarios will things fail?

  • 1. Only works with public modifiers
  • 2. The default detection exception for @ Transaction is RuntimeException and its subclasses. If there are other exceptions that need to be rolled back, you need to manually configure them, for example: @ transactional (rollbackfor)= Exception.class )
  • 3. Make sure that the exception is not tried catch {}, and the catch will not be rolled back later
  • 4. Check whether the database supports transactions, such as mysql
  • 5. By default, SpringBoot projects already support transactions without configuration; other types of projects need to configure whether to enable transactions in xml
  • 6. If a non @ Transaction method call has @ Transaction in the same class, it will not take effect because of the agent problem

Here, in the same class, a non @ Transaction method calls a method with @ Transaction will not take effect. If it's a method call in the same class, it won't be intercepted by the method interceptor, so the Transaction won't work. You have to put the method into another class, and the class is injected through Spring.

Spring uses dynamic proxy (AOP) to manage and slice beans. It generates a proxy object for each class. Only when the proxy objects are called, can the aspect logic be triggered.

In the same class, method B calls method A of the event meta object, not through the proxy object, so Spring can't switch to this call, that is, it can't guarantee transactional through annotation.

Reference address

If you like my article, you can pay attention to the personal subscription number. Welcome to leave a message and communicate at any time. If you want to join the wechat group and discuss it together, please add the administrator Jianzhan culture - little assistant (lastpass4u), who will pull you into the group.

Posted by stylefrog on Mon, 29 Jun 2020 03:15:21 -0700