Official account: Java Xiaokaxiu, website: Javaxks.com

Author: to the north, links: blog.csdn.net/qq_20597727/article/details/84900994

Transactional failure scenario

The first kind of

The Transactional annotation does not work when the annotation method modifier is not public. For example, the following code.

Define an incorrect implementation of the @Transactional annotation that decorates a method of the default accessor

/**
 * @author zhoujy
 * @date 2018年12月06日
 **/
@Component
public class TestServiceImpl {
    @Resource
    TestMapper testMapper;
    
    @Transactional
    void insertTestWrongModifier() {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }

}
Copy the code

Within the same package, create a new call object for access.

@Component public class InvokcationService { @Resource private TestServiceImpl testService; Public void invokeInsertTestWrongModifier () {/ / call @ the default access operators of Transactional annotation method testService. InsertTestWrongModifier (); }}Copy the code

The test case

@RunWith(SpringRunner.class) @SpringBootTest public class DemoApplicationTests { @Resource InvokcationService invokcationService; @Test public void testInvoke(){ invokcationService.invokeInsertTestWrongModifier(); }}Copy the code

Testmapper. insert(new Test(10,20,30)); insert(new Test(10,20,30)); Action is not rolled back. Testmapper. insert(new Test(10,20,30)); A rollback will take place.

The second,

Calls within the class that call the @Transactional annotation within the class. In this case, the transaction will not be opened. The sample code is as follows.

Set up an internal call

/** * @author zhoujy * @date 2018年12月06日 **/ @Component public class TestServiceImpl implements TestService {@Resource TestMapper testMapper; Testmapper.insert (new Test(10,20,30)); insert(new Test(10,20,30)); if (re > 0) { throw new NeedToInterceptException("need intercept"); } testMapper. Insert (new Test 210, 30 ()); } public void testInnerInvoke(){// Call the @Transactional method within the class. insertTestInnerInvoke(); }}Copy the code

Test cases.

@RunWith(SpringRunner.class) @SpringBootTest public class DemoApplicationTests { @Resource TestServiceImpl testService; /* Test public void testInnerInvoke(){// Test the Transactional method to Test the Transactional method //testService.insertTestInnerInvoke(); / / test internal call transaction method whether normal testService testInnerInvoke (); }}Copy the code

Testmapper.insert (new Test(10,20,30)) will be rolled back.

Then run another test case that calls a method within the class that calls the Transactional method annotated internally by @Transactional. The result is that the transaction does not open properly. Testmapper.insert (new Test(10,20,30)) operations will be saved to the database without rollback.

The third kind of

The transaction method catches the exception internally, and no new exception is thrown, resulting in the transaction operation not being rolled back. The sample code is as follows.

/** * @author zhoujy * @date 2018年12月06日 **/ @Component public class TestServiceImpl implements TestService {@Resource TestMapper testMapper; @transactional public void insertTestCatchException() {try {int re = testmapper. insert(new Test(10,20,30)); If (re > 0) {throw new NeedToInterceptException(" Need Intercept "); } testMapper. Insert (new Test 210, 30 ()); }catch (Exception e){ System.out.println("i catch exception"); }}}Copy the code

The test case code is as follows.

@RunWith(SpringRunner.class) @SpringBootTest public class DemoApplicationTests { @Resource TestServiceImpl testService; @Test public void testCatchException(){ testService.insertTestCatchException(); }}Copy the code

Running the Test case found that although an exception was thrown, it was caught and not thrown out of the method, and the testmapper.insert (new Test(210,20,30)) operation was not rolled back.

These three are the main reasons why the @Transactional annotation does not work. Let’s take a look at why the @Transactional annotation doesn’t work with the spring annotation implementation source code.

The @Transactional annotation does not work with theoretical analysis

For those of you not familiar with the @Transactional annotation implementation, take a look at another article, @Transactional Annotation implementation, and then start analyzing the following three scenarios using source code.

The first kind of

The @Transactional annotation will not work when the annotation method modifier is not public. The @Transactional annotation is implemented using a dynamic proxy. The @Transactional annotation is implemented using a proxy object created during bean initialization. There is a process for Spring to scan the @Transactional annotation for information. Unfortunately, methods marked with @Transactional annotations that are not public default to empty @Transactional information. There will be no proxy object creation to the bean or no proxy call to the method

The @Transactional annotation explains how to determine whether a bean can create a proxy object. According to spring created an aop tangent point BeanFactoryTransactionAttributeSourceAdvisor instance, traverse the current bean methods of class object, Check whether the annotation information above the method contains the @Transactional annotation information. If any of the bean methods contains the @Transactional annotation information, It is fit the BeanFactoryTransactionAttributeSourceAdvisor tangent point. The proxy object needs to be created, and then the proxy logic opens and closes the logic for us to manage the transaction.

The following method is used in spring source code to intercept the bean creation process and find the point at which the bean ADAPTS. The purpose of this method is to find @Transactional information ona method. It means the point of tangency BeanFactoryTransactionAttributeSourceAdvisor can dog application (canApply) to a bean,

  • AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class
    , boolean)
public static boolean canApply(Pointcut pc, Class<? > targetClass, boolean hasIntroductions) { Assert.notNull(pc, "Pointcut must not be null"); if (! pc.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); if (methodMatcher == MethodMatcher.TRUE) { // No need to iterate the methods if we're matching any method anyway... return true; } IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } // Set< class <? >> classes = new LinkedHashSet<Class<? >>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class<? > clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); for (Method method : methods) { if ((introductionAwareMethodMatcher ! = null && introductionAwareMethodMatcher.matches(method, targetClass, HasIntroductions) | | / / query methods on @ Transactional annotation information methodMatcher. Matches (method, targetClass)) {return true; } } } return false; }Copy the code

We can debug the trace code step by step at the above method break point, and finally the above code will call the following method to determine. A breakpoint on the method below is also a good way to trace back to the method call stack.

  • AbstractFallbackTransactionAttributeSource#getTransactionAttribute
    • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, Class<? > targetClass) {// Don't allow no public methods as required. Return @transactional message always null if (allowPublicMethodsOnly() &&! Modifier.isPublic(method.getModifiers())) { return null; } //....... is omitted }Copy the code

No proxy object is created

So, if all modifiers on methods are non-public, no proxy object will be created. For example, in the original test code, if the testService of the normal modifier is the cglib-created proxy object shown in the following image.

If the methods in class are non-public then they are not proxy objects.

No proxy invocation is made

Consider a case, as shown in the code below. Both methods are annotated by the @Transactional annotation, but one has a public modifier and the other does not. In this case, we can expect to create proxy objects because there is at least one @Transactional annotation method with a public modifier.

Does insertTestWrongModifier open a transaction when a proxy object is created? The answer is no.

/** * @author zhoujy * @date 2018年12月06日 **/ @Component public class TestServiceImpl implements TestService {@Resource TestMapper testMapper; @override @transactional public void insertTest() {int re = testmapper. insert(new Test(10,20,30)); if (re > 0) { throw new NeedToInterceptException("need intercept"); } testMapper. Insert (new Test 210, 30 ()); } @transactional void insertTestWrongModifier() {int re = testmapper. insert(new Test(10,20,30)); if (re > 0) { throw new NeedToInterceptException("need intercept"); } testMapper. Insert (new Test 210, 30 ()); }}Copy the code

Reason is that the dynamic proxy objects agent logic called, in additional to create a proxy object interception function CglibAopProxy. DynamicAdvisedInterceptor# intercept, there is a logic as follows, The goal is to get the method-adapted AOP logic that currently needs to be executed for the currently proxied object.

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Copy the code

Finding aop logic procedures for the @Transactional annotation is similarly performed once

  • AbstractFallbackTransactionAttributeSource#getTransactionAttribute
    • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

This means that you need to find the @Transactional annotation information ona method. If you don’t implement the @Transactional proxy logic, execute the method directly. Without the @Transactional annotation proxy logic, you cannot start a transaction, as discussed in the previous article.

The second,

Calls within the class that call the @Transactional annotation within the class. In this case, the transaction will not be opened.

After a detailed analysis of the first, you can probably guess why transaction management is not turned on in this case;

Since the transaction management is based on dynamic proxy agent logic implementation of an object, if internal transaction method inside the class, this process of call transaction method is not invoked through a proxy object, but through this object to call methods directly, bypassing the proxy object, is certainly no agent logic.

In fact, we can play this way, the internal call can also be implemented to open the transaction, the code is as follows.

/** * @author zhoujy * @date 2018年12月06日 **/ @Component public class TestServiceImpl implements TestService {@Resource TestMapper testMapper; @Resource TestServiceImpl testServiceImpl; @transactional public void insertTestInnerInvoke() {int re = testmapper.insert (new Test(10,20,30)); if (re > 0) { throw new NeedToInterceptException("need intercept"); } testMapper. Insert (new Test 210, 30 ()); } public void testInnerInvoke () {/ / internal call transaction method testServiceImpl. InsertTestInnerInvoke (); }}Copy the code

The above is the use of proxy objects for transaction invocation, so can enable transaction management, but in practice, no one will be idle to play this way

The third kind of

The transaction method catches the exception internally, and no new exception is thrown, resulting in the transaction operation not being rolled back.

In this case, we may be more common, the problem is in proxy logic, let’s first look at the source code to show how dynamic proxy logic for us to manage transactions, this process is mentioned in another article of mine.

  • TransactionAspectSupport#invokeWithinTransaction

Here’s the code.

protected Object invokeWithinTransaction(Method method, Class<? > targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); if (txAttr == null || ! (tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction And commit/rollback calls. / / open transaction TransactionInfo txInfo = createTransactionIfNecessary (tm, txAttr. joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked retVal = invocation.proceedWithInvocation(); } Catch (Invocation) {// Target Invocation exception Roll back the transaction in the catch logic completeTransactionAfterThrowing (txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } / / to commit the transaction commitTransactionAfterReturning (txInfo); return retVal; } else { //.................... }}Copy the code

So it is clear from the above code that for a transaction to be rolled back, it must be able to catch an exception here. If the exception is caught midway, the transaction will not be rolled back.

Summarize the above situations ~~~~~~~~~~~~~~~~~~~~~