The rollover accident caused by Lombok is too bad!

#background

Two days ago, Tiezi asked such a question in the group:

be3e8c674896ddb8f3c4eebe3c7ae7fd.png

I have stepped on this pit before; earlier, the Lombok plug-in was introduced into the project, which really liberated my hands and replaced some repetitive simple work (writing methods such as Getter, Setter, and toString).

However, in the process of using it, I also found some problems. At the beginning, I didn’t realize that it was a problem with Lombok. Later, I tracked the source code of other corresponding components and found out that it was a problem with Lombok!

# 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 description is as follows:

We have an entity class:

@Data
public class NMetaVerify{
    private NMetaType nMetaType;
    private Long id;
    ...other attributes
}

When we use Mybatis to insert data, we find that other attributes can be inserted normally, but the nMetaType attribute is always null in the database.

Resolve

When I debugged the 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 field of the database was always null. I thought It is because my enumeration type is written incorrectly. After looking at other fields 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 this nMetaType attribute, using the getxxxx method to obtain it, but I found that the get method of nMetaType seems a bit longer than the getxxxx method required by Mybatis It seems to be different, the problem has been found!

Reason

The get-set method generated by Lombok for attributes with lowercase first letter and uppercase second letter is different from that generated by Mybatis and idea or the officially recognized get-set method of Java:

Get-Set method generated by Lombok

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

The official default behavior of idea, Mybatis, and Java 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;




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, directly intercepted from the second (index) (simple and rude)
          name = name. substring(2);
      } else if (name.startsWith("get") || name.startsWith("set")) {//set-get starts to intercept from the third (index)
          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 an attribute 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 is 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 Attribute name: NMetaType

Solution

  • Modify the attribute name, make the second letter lowercase, or stipulate that the first two letters of all attributes must be lowercase

  • 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 the get-set method for this special attribute

# @Accessor(chain = true) annotation problem

Problem discovery

When using easyexcel to export, I found that the previous entity class export was normal, but now the newly added entity class is not normal. After comparison, I found that the newly added entity class added @Accessor(chain = true) Note, our purpose is mainly to facilitate our chain call set method:

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

Reason

The bottom layer of easyexcel uses cglib as the reflection toolkit:

Line 130 of class com.alibaba.excel.read.listener.ModelBuildEventListener
BeanMap.create(resultModel).putAll(map);


The bottom layer is the method call of cglib's BeanMap


abstract public Object put(Object bean, Object key, Object value);

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

  • Remove the Accessor annotation.

  • Either wait for the author of easyexcel to replace the underlying cglib or something else, anyway, just copy the code to support the setxxx method whose return value is not void.

d8f327eec4cbb1bd228c9035c2736619.jpeg

Do you really know the function and principle of SpringBoot Starter?

\

An Online Fault: Thoughts on Database Connection Pool Leakage

c6c97a4b71498b32cdd130c8d06dd973.jpeg

Interviewer: What are the key points in designing a high-traffic and high-concurrency system?

35376f5a51ab87052d0c40430ba618b9.jpeg

What is the fuse mechanism of Spring Cloud microservices and the meaning of fuse?

76746818d4938e5c1d4ccf26083e044a.jpeg

Java project online JVM tuning practice, FullGC greatly reduced

f6a5e26261073627902340efe775bb85.jpeg

SpringBoot integrates Canal and RabbitMQ to monitor data changes

78cd839f113bf1f81458f9ba516865f4.gif

ReplyDry goods】Get selected dry goods video tutorials

ReplyAdd group】Join the exchange group for solving difficult problems

Replymat】A detailed document tutorial for memory overflow problem analysis

ReplyMake money】Get a WeChat robot that can make money in java

ReplySideline】Get a programmer’s sideline strategy

1dec2b97be43e7f7f06c0a85acb492ac.jpeg

Good article, please like + share

ad2d89005ba16ff5f54343b44665b71a.gif

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge Java skill tree Using JDBC to operate the databaseDatabase operation 118281 people are learning the system