“This is the fifth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

The product now expects the User creation and saving logic to be separated: the User instance creation and saving logic to be separated into two separate methods. The Transactional annotation @Transactional is then applied to the method that holds the database.

@Service
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private StudentService studentService;

    public void saveStudent(String realname) throws Exception {
        Student student = new Student();
        student.setRealname(realname);
        studentService.doSaveStudent(student);
    }

    @Transactional
    private void doSaveStudent(Student student) throws Exception {
        studentMapper.saveStudent(student);
        if (student.getRealname().equals("Xiao Ming")) {
            throw new RuntimeException("The user already exists"); }}}Copy the code

Execute the program, exception normally thrownThe transaction was not rolled back

The source code parsing

The debug:The previous section is the process of Spring creating the Bean. After the Bean is initialized, the agent operation is attempted, starting with the following method:

AbstractAutoProxyCreator#postProcessAfterInitialization

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if(bean ! =null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
Copy the code

Continue debugging until

AopUtils#canApply

Determines whether the method can be used to create a proxy based on conditions in the aspect definition. Matches (method, targetClass) matches this method to check whether it matches the following criteria:

public static boolean canApply(Pointcut pc, Class<? > targetClass,boolean hasIntroductions) {
   // ...
   for(Class<? > clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {
         if(introductionAwareMethodMatcher ! =null ?
               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
               methodMatcher.matches(method, targetClass)) {
            return true; }}}return false;
}
Copy the code

From matches() to

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

Gets the transaction attributes in the annotation and determines the transaction strategy based on the attributes.And then call to

computeTransactionAttribute

Determine whether to return transaction attributes based on method and class types:When the condition is true, null is returned, indicating that the method will not be propped and the transaction annotations will not take effect.

Is that true or not?

Condition 1: allowPublicMethodsOnly()

AnnotationTransactionAttributeSource# publicMethodsOnly attribute valuesPublicMethodsOnly is initialized by AnnotationTransactionAttributeSource constructor, the default is true.

Condition 2: Modifier.ispublic ()

According to the incoming method. GetModifiers () method of the Modifier, the Modifier is Java. Lang. Reflect. Modifier static properties, corresponding to a few kinds of Modifier are respectively:

  • PUBLIC: 1
  • PRIVATE: 2
  • PROTECTED: 4

A bit operation is done to return true only if the method modifier passed in is of type publicIn summary, Spring handles the annotation only if the transaction method is public.

correction

Just change the modifier from private to public. In fact, IDEA also generates an alarm, which is generally avoided.To call the transaction-annotated method, you must call the method propped up by Spring AOP: not through an internal call of the class or through this. So in our case, the StudentService Autowired an instance of itself to complete the proxy method call.