The problem of using the pit of the Lombok Setter-Getter method and the @Accessor (chain = true) annotation

1. The pit of the Setter-Getter method

  • problem discovery

We mainly use the annotation of Lombok’s Setter-Getter method in the project, that is, the combined annotation @Data, but in the process of using Mybatis to insert data, a problem occurred. The problem is described as follows

When we used Mybatis to insert data, we found that other attributes were inserted normally, but the nMetaType attribute was always null in the database.

  • solve
    When I debugged the program project code to call the corresponding method of inserting SQL in Mybatis, I saw that the nMetaType attribute of the NMetaVerify object still had data, but after the insertion was executed, the nMetaType of the database was always null. I thought it was me The wording of the enumeration type is incorrect. After reading other subdivisions with the same enumeration type, they can also be inserted into the database normally, which makes me feel even more confused. So, I tracked the source code of Mybatis and found that Mybatis used reflection when obtaining the nMetaType attribute, and used the getxxxx method to obtain it, but I found that the get method of nMetaType seemed a bit different from the getxxxx method required by Mybatis. Same. Problem found!
  • reason
    The get-set method generated by Lombok for the first letter lowercase and the second letter uppercase attribute is different from that generated by Mybatis and the idea or the get-set method officially recognized by Java:
#Lombok generated Get-Set method
@Data
public class NMetaVerify {<!-- -->
    private Long id;
    private NMetaType nMetaType;
    private Date createTime;

    public void lombokFound(){<!-- -->
        NMetaVerify nMetaVerify = new NMetaVerify();
        nMetaVerify.setNMetaType(NMetaType.TWO); //Note: the set method of nMetaType is setNMetaType, the first n letter is capitalized,
        nMetaVerify.getNMetaType(); //getxxxx method is also capitalized
    }
}

#idea, Mybatis, Java's official default behavior is:
public class NMetaVerify {<!-- -->
    private Long id;
    private NMetaType nMetaType;
    private Date createTime;

    public Long getId() {<!-- -->
        return id;
    }

    public void setId(Long id) {<!-- -->
        this.id = id;
    }

    public NMetaType getnMetaType() {<!-- -->//Note: the first letter of the nMetaType attribute is lowercase
        return nMetaType;
    }

    public void setnMetaType(NMetaType nMetaType) {<!-- -->//Note: the first letter of the nMetaType attribute is lowercase
        this.nMetaType = nMetaType;
    }

    public Date getCreateTime() {<!-- -->
        return createTime;
    }

    public void setCreateTime(Date createTime) {<!-- -->
        this.createTime = createTime;
    }
}

Mybatis (version 3.4.6) parses the get-set method to obtain the source code of the attribute name:

package org.apache.ibatis.reflection.property;

import java.util.Locale;

import org.apache.ibatis.reflection.ReflectionException;

/**
 * @author Clinton Begins
 */
public final class PropertyNamer {<!-- -->

     private PropertyNamer() {<!-- -->
         // Prevent Instantiation of Static Class
       }

    public static String methodToProperty(String name) {<!-- -->
      if (name.startsWith("is")) {<!-- -->//The beginning of is is generally bool type, and it is directly intercepted from the second (index) (simple and rude)
          name = name. substring(2);
      } else if (name.startsWith("get") || name.startsWith("set")) {<!-- -->//set-get starts from the third (index) intercept
          name = name. substring(3);
      } else {<!-- -->
          throw new ReflectionException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'.");
      }
           //The following judgment is very important. It can be divided into two sentences to start the explanation. The explanation is as follows
            //First sentence: name.length()==1
            // For properties with only one letter, such as private int x;
            // The corresponding get-set method is getX();setX(int x);
            //Second sentence: name.length() > 1 & amp; & amp; !Character.isUpperCase(name.charAt(1)))
            // The length of the attribute name is greater than 1, and the second (charAt(1) in the code, this 1 is the array subscript) letter is lowercase
            // If the second char is uppercase, then return name directly
      if (name.length() == 1 || (name.length() > 1 & amp; & amp; !Character.isUpperCase(name.charAt(1)))) {<!-- -->
          name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);//Make the first letter of the attribute name lowercase, and then add the following content
      }

      return name;
    }

    public static boolean isProperty(String name) {<!-- -->
       return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
    }

    public static boolean isGetter(String name) {<!-- -->
       return name.startsWith("get") || name.startsWith("is");
    }

    public static boolean isSetter(String name) {<!-- -->
       return name.startsWith("set");
    }

}

Mybatis parses the get-set method as a property name test

 @Test
    public void foundPropertyNamer() {<!-- -->
        String isName = "isName";
        String getName = "getName";
        String getnMetaType = "getnMetaType";
        String getNMetaType = "getNMetaType";

        Stream.of(isName, getName, getnMetaType, getNMetaType)
                .forEach(methodName->System.out.println("The method name is:" + methodName + "Property name:" + PropertyNamer.methodToProperty(methodName)));
    }

    #The output results are as follows:
    The method name is: isName attribute name: name
    The method name is: getName attribute name: name
    The method name is: getnMetaType Attribute name: nMetaType //The second letter of this and the following attributes is capitalized, so return name directly
    The method name is: getNMetaType Property name: NMetaType
  • solution
    1. Modify the attribute name, make the second letter lowercase, or stipulate that the first two letters of all attributes must be lowercase
    2. If the database has been designed, and the front-end and back-end interfaces are well connected, and you don’t want to modify it, then use idea to generate get-set methods for this special attribute

2. Problems with @Accessor (chain = true) annotation
When using easyexcel (github.com/alibaba/eas…) to convert, I found that the previous entity class conversion was normal, but now the newly added entity class is not normal. After comparison, I found that the newly added entity class has increased @Accessor (chain = true) annotation, our purpose is mainly to facilitate our chain call set method:

new UserDto()
.setUserName("")
.setAge(10)
?…
.setBirthday(new Date());

easyexcel uses cglib as a reflection toolkit in the short term, but cglib uses a method of the Introspector class in Java’s rt.jar:

# Introspector.java line 520
if (int.class.equals(argTypes[0]) & amp; & amp; name.startsWith(GET_PREFIX)) {<!-- -->
   pd = new IndexedPropertyDescriptor(this. beanClass, name. substring(3), null, null, method, null);
   //The following line of judgment only gets the setxxxx method whose return value is void type
 } else if (void. class. equals(resultType) & amp; & amp; name. startsWith(SET_PREFIX)) {<!-- -->
    // Simple setter
    pd = new PropertyDescriptor(this. beanClass, name. substring(3), null, method);
    if (throwsException(method, PropertyVetoException. class)) {<!-- -->
       pd.setConstrained(true);
    }
}
  • solution
    1. Remove the Accessor annotation
    2. Either wait for the author of easyexcel to replace the underlying cglib or something else, anyway, it is enough to support the setxxx method whose return value is not void