3 scenarios and solutions for transaction annotation @Transactional failure

Click on the blue font above and select "Star Official Account"

High-quality articles, delivered immediately

Follow the official account backstage to reply to pay or mall to obtain actual project information + video

Author: North

blog.csdn.net/qq_20597727/article/details/84900994

Introduction to Transactional Failure Scenarios

The first

When the Transactional annotation marks the method modifier as non-public, the @Transactional annotation will not work. For example, the following code.

Define an incorrect @Transactional annotation implementation to modify a method of 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));
    }

}

In the same package, create a new calling object for access.

@Component
public class InvokcationService {
    @Resource
    private TestServiceImpl testService;
    public void invokeInsertTestWrongModifier(){
        //调用@Transactional标注的默认访问符方法
        testService.insertTestWrongModifier();
    }
}

Test case

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
   @Resource
   InvokcationService invokcationService;

   @Test
   public void  testInvoke(){
      invokcationService.invokeInsertTestWrongModifier();
   }
}

The above access method results in the transaction not being opened, so when the method throws an exception, the testMapper.insert(new Test(10,20,30)); operation will not be rolled back. If the TestServiceImpl#insertTestWrongModifier method is changed to public, the transaction will be opened normally, and testMapper.insert(new Test(10,20,30)); will be rolled back.

The second

Invoke the method marked with @Transactional inside 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;

    @Transactional
    public void insertTestInnerInvoke() {
        //正常public修饰符的事务方法
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }


    public void testInnerInvoke(){
        //类内部调用@Transactional标注的方法。
        insertTestInnerInvoke();
    }

}

Test case.

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

   @Resource
   TestServiceImpl testService;

   /**
    * 测试内部调用@Transactional标注方法
    */
   @Test
   public void  testInnerInvoke(){
       //测试外部调用事务方法是否正常
      //testService.insertTestInnerInvoke();
       //测试内部调用事务方法是否正常
      testService.testInnerInvoke();
   }
}

The above is the test code used. Running the test knows that the external call transaction method can start the transaction, and the testMapper.insert(new Test(10,20,30)) operation will be rolled back;

Then run another test case, call a method inside the class to call the internal transaction method marked by @Transactional, the result of the operation is that the transaction will not start normally, and the testMapper.insert(new Test(10,20,30)) operation will be saved There will be no rollback to the database.

The third

The transaction method internally caught the exception and did not throw a new exception, causing the transaction operation to not be 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,20,30));
        }catch (Exception e){
            System.out.println("i catch exception");
        }
    }
    
}

The test case code is as follows.

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

   @Resource
   TestServiceImpl testService;

   @Test
   public void testCatchException(){
      testService.insertTestCatchException();
   }
}

Running the test case found that although an exception was thrown, the exception was caught and not thrown outside the method. The testMapper.insert(new Test(210,20,30)) operation did not roll back.

The above three are the main reasons why the @Transactional annotation does not work and the @Transactional annotation fails. The following is a source code analysis of the @Transactional annotation in spring, why the @Transactional annotation does not work.

@Transactional annotation does not work principle analysis

First of all, I don't understand the implementation principle of @Transactional annotation, and then start to analyze the following three situations in combination with the source code.

The first

When the @Transactional annotation marks the method modifier as non-public, the @Transactional annotation will not work. The reason for the analysis here is that @Transactional is implemented based on a dynamic proxy. The implementation method is analyzed in the @Transactional annotation implementation principle. During the bean initialization process, a proxy object is created for the bean instance with @Transactional annotation. There is a spring scan here. The process of @Transactional annotating information is unfortunately reflected in the source code. If the modifier of the @Transactional method is not public, then the @Transactional information of the default method is empty, then the bean will not be created or the proxy object will not be created. Method for proxy call

In the realization principle of @Transactional annotation, it is introduced how to determine whether a bean creates a proxy object. The approximate logic is. Create an aop pointcut BeanFactoryTransactionAttributeSourceAdvisor instance according to spring, traverse the method objects of the current bean class, and determine whether the annotation information on the method contains @Transactional. If any method of the bean contains @Transactional annotation information, then it is adapted to this BeanFactoryTransactionAttributeSourceAdvisor cutpoint . You need to create a proxy object, and then the proxy logic manages the transaction opening and closing logic for us.

In the spring source code, when intercepting the bean creation process and looking for the point of bean adaptation, the following method is used, the purpose is to find the @Transactional information on the method, if there is, it means that the point of BeanFactoryTransactionAttributeSourceAdvisor can be applied (canApply) Into the 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;
   }

    //遍历class的方法对象
   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)) ||
             //适配查询方法上的@Transactional注解信息  
             methodMatcher.matches(method, targetClass)) {
            return true;
         }
      }
   }

   return false;
}

We can interrupt the above method to debug the tracking code step by step, and finally the above code will call the following methods to judge. Breakpoints on the following methods, looking back at the method call stack is also a good way to trace.

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
   // Don't allow no-public methods as required.
   //非public 方法,返回@Transactional信息一律是null
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
   }
   //后面省略.......
 }

Do not create proxy objects

Therefore, if the modifiers on all methods are non-public, the proxy object will not be created. Take the initial test code as an example, if the normal modifier testService is the proxy object created by cglib in the picture below.

If the methods in the class are all non-public, it will not be a proxy object.

No proxy call

Consider a situation, as shown in the following code. Both methods are annotated with @Transactional annotations, but one has a public modifier and the other does not. Then, if we can foresee this situation, a proxy object will be created because there is at least one @Transactional annotation method with a public modifier.

Once the proxy object is created, will insertTestWrongModifier start the transaction? 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,20,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,20,30));
    }

 


}

The reason is that when the dynamic proxy object makes proxy logic calls, in the interception function of the proxy object created by cglib CglibAopProxy.DynamicAdvisedInterceptor#intercept, there is a logic as follows, the purpose is to obtain the aop logic of the method adaptation of the current proxy object that needs to be executed.

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

For the @Transactional annotation to find the aop logic process, similarly, it is also executed once

  • AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

That is to say, you also need to find the @Transactional annotation information on a method. If not, the proxy logic corresponding to the proxy @Transactional will not be executed, and the method will be executed directly. Without the @Transactional annotation proxy logic, the transaction cannot be opened, which is also mentioned in the previous article.

The second

Invoke the method marked with @Transactional inside the class. In this case, the transaction will not be opened.

After a detailed analysis of the first type, why not open transaction management in this situation, the reason should be guessed;

Since the transaction management is based on the proxy logic of the dynamic proxy object, if the transaction method inside the class is called inside the class, the process of calling the transaction method is not called through the proxy object, but directly through the this object to call the method , The bypassed proxy object, there must be no proxy logic.

In fact, we can play like this, and internal calls can also enable transactions. 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,20,30));
    }


    public void testInnerInvoke(){
        //内部调用事务方法
        testServiceImpl.insertTestInnerInvoke();
    }

}

The above is the use of proxy objects for transaction calls, so transaction management can be turned on, but in actual operation, no one will be idle like this~

The third

The transaction method internally caught the exception and did not throw a new exception, causing the transaction operation to not be rolled back.

In this case, we may be more common. The problem lies in the proxy logic. Let's take a look at how the dynamic proxy logic in the source code manages affairs for us. This process is mentioned in another article of mine.

  • TransactionAspectSupport#invokeWithinTransaction

code show as below.

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.
       //开启事务
      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 (Throwable ex) {
         // target invocation exception
          //异常时,在catch逻辑中回滚事务
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }
       //提交事务
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }

   else {
     //....................
   }
}

So looking at the above code, it is clear at a glance. If the transaction wants to roll back, it must be able to catch the exception here. If the exception is caught midway, then the transaction will not be rolled back.

Summarized the above situations


There are hot recommendations???

SpringBoot: Aspect AOP to achieve permission verification: example demonstration and full explanation of annotations

Our company has used 7 years of code execution time-consuming statistical functions, so elegant! !

19 pictures overview Spring Cloud

The new CTO prohibits the separation of front and back ends, and also said a lot of advantages and disadvantages!

It is not difficult to realize SpringBoot's simple read-write separation from scratch!

Use VS Code to develop Spring Boot, Zhenni Ma show!

Stupidly indistinguishable Cookie, Session, Token, JWT

Dry goods|SpringBoot integrated Jiguang push complete implementation code (recommended collection)

Zhihu Gaozan: Which one to choose between Pinduoduo and State Grid Offer?

Don't use SELECT *

Click to read the original text, go to learn SpringCloud actual combat project

Guess you like

Origin blog.csdn.net/qq_17231297/article/details/115344564