Mybatis creation return object exception caused by Lombok’s @Builder annotation

Mybatis creation return object exception caused by Lombok’s @Builder annotation

**Conclusion:** After using the Bbuilder annotation, a fully parameterized constructor will be produced based on the current class (excluding the attributes of the parent class). Mybaitis will cause many strange exceptions when it attributes the returned data, so it is best not to use it. @Builder annotation, if you want to use it, write one yourself and bring a default no-argument constructor.

1.The role of @Builder

After using the @Builder annotation, the instance of the created object cannot be new directly. It can only be created through XXXX.builder().build(). Even if you directly write a parameterless constructor, the compilation will not pass. (outrageous)

@Data
@ToString
@Builder
public class UserDO extends BaseDO {<!-- -->

  public UserDO() {<!-- -->

  }

  private static final long serialVersionUID = 1643909999676303799L;

  private String username;

  private String password;

  private String name;

  private String tel;

  private String isTel() {<!-- -->
    return this.tel;
  }

  private String isT() {<!-- -->
    return this.tel;
  }

*E*rror:(29, 1) java: Cannot apply constructor UserDO in class org.apache.ibatis.test.domain.UserDO to the given type; * *Requires: no parameters* *Found: java.lang.String,java.lang.String,java.lang.String,java.lang.String* Reason: The length of the actual parameter list and the formal parameter list are different*

Compiled entity class (idea decompilation)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//


public class UserDO extends BaseDO {<!-- -->
    private static final long serialVersionUID = 1643909999676303799L;
    private String username;
    private String password;
    private String name;
    private String tel;

    private String isTel() {<!-- -->
        return this.tel;
    }

    private String isT() {<!-- -->
        return this.tel;
    }
\t
    // No modifier, protected by default
    UserDO(String username, String password, String name, String tel) {<!-- -->
        this.username = username;
        this.password = password;
        this.name = name;
        this.tel = tel;
    }
\t
    
    public static UserDO.UserDOBuilder builder() {<!-- -->
        return new UserDO.UserDOBuilder();
    }
\t
    // builder object
    public static class UserDOBuilder {<!-- -->
        private String username;
        private String password;
        private String name;
        private String tel;

        UserDOBuilder() {<!-- -->
        }

        public UserDO.UserDOBuilder username(String username) {<!-- -->
            this.username = username;
            return this;
        }

        public UserDO.UserDOBuilder password(String password) {<!-- -->
            this.password = password;
            return this;
        }

        public UserDO.UserDOBuilder name(String name) {<!-- -->
            this.name = name;
            return this;
        }

        public UserDO.UserDOBuilder tel(String tel) {<!-- -->
            this.tel = tel;
            return this;
        }
// The build method creates the entity object
        public UserDO build() {<!-- -->
            return new UserDO(this.username, this.password, this.name, this.tel);
        }

        public String toString() {<!-- -->
            return "UserDO.UserDOBuilder(username=" + this.username + ", password=" + this.password + ", name=" + this.name + ", tel=" + this. tel + ")";
        }
    }
}

2. How does Mybaitis encapsulate the returned data into the corresponding entity class

? 2.1. Create corresponding entity classes through reflection

? 2.2. Set the attributes to the entity class attributes according to the corresponding attribute information in the resultmap.

Create return instance

org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.util.List>, java.util.List, java.lang.String)

 /**
   *
   * @param rsw ResultSe packaging
   * @param resultMap
   * @param constructorArgTypes constructor type collection
   * @param constructorArgs constructor collection
   * @param columnPrefix database field prefix
   * @return
   * @throws SQLException
   */
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
    throws SQLException {<!-- -->
    //Return body class type
  final Class<?> resultType = resultMap.getType();
   // Metadata class created based on class type
  final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    // Constructor mapping collection
  final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
  //The current class type exists in the TypeHandler collection
    if (hasTypeHandlerForResultObject(rsw, resultType)) {<!-- -->
       // Directly parse and return through TypeHandler
    return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
  } else if (!constructorMappings.isEmpty()) {<!-- -->
        //Create according to the constructor mapping collection
    return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
  } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {<!-- -->
       //generated by default constructor
    return objectFactory.create(resultType);
  } else if (shouldApplyAutomaticMappings(resultMap, false)) {<!-- -->
       // Constructor generates
    return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
  }
  throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

The class that creates instances in mybatisDefaultObjectFactory

There are two create methods, which can create objects based on parameterless and parameterized constructors.

//Interface implemented by DefaultObjectFactory
public interface ObjectFactory {<!-- -->

  /**
   * Sets configuration properties.
   * @param properties configuration properties
   */
  default void setProperties(Properties properties) {<!-- -->
    // NOP
  }

  /**
   * Creates a new object with default constructor.
   *
   * @param <T>
   * the generic type
   * @param type
   *Object type
   * @return the t
   */
  <T> T create(Class<T> type);

  /**
   * Creates a new object with the specified constructor and params.
   *
   * @param <T>
   * the generic type
   * @param type
   *Object type
   * @param constructorArgTypes
   * Constructor argument types
   * @param constructorArgs
   * Constructor argument values
   * @return the t
   */
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

  /**
   * Returns true if this object can have a set of other objects.
   * It's main purpose is to support non-java.util.Collection objects like Scala collections.
   *
   * @param <T>
   * the generic type
   * @param type
   *Object type
   * @return whether it is a collection or not
   * @since 3.1.0
   */
  <T> boolean isCollection(Class<T> type);

}

How to create an object

private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {<!-- -->
  try {<!-- -->
      // Created according to newInstance() of the constructor method
    Constructor<T> constructor;
    if (constructorArgTypes == null || constructorArgs == null) {<!-- -->
        // No parameter construction
      constructor = type.getDeclaredConstructor();
      try {<!-- -->
        return constructor.newInstance();
      } catch (IllegalAccessException e) {<!-- -->
        if (Reflector.canControlMemberAccessible()) {<!-- -->
          constructor.setAccessible(true); // Modify modifier properties
          return constructor.newInstance();
        } else {<!-- -->
          throw e;
        }
      }
    }
      //The first constructor passed in
    constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
    try {<!-- -->
        // Create a constructor with parameters
      return constructor.newInstance(constructorArgs.toArray(new Object[0]));
    } catch (IllegalAccessException e) {<!-- -->
      if (Reflector.canControlMemberAccessible()) {<!-- -->
        constructor.setAccessible(true);
        return constructor.newInstance(constructorArgs.toArray(new Object[0]));
      } else {<!-- -->
        throw e;
      }
    }
  } catch (Exception e) {<!-- -->
    String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
        .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
    String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
        .stream().map(String::valueOf).collect(Collectors.joining(","));
    throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
  }
}

3. Links that generate exceptions

? 3.1. Instances created through parameterized constructors cause parameter type exceptions

The type of the field returned by the query and the data type of the constructor parameter are matched in order, which will cause type inconsistency problems.

? 3.2. The number of attributes created by the instance created through the parameterized constructor is less than the length of the returned data attribute.

? If your query statement is select id from xxx where xxx

? But the construction method of your do object has two input parameters. At this time, the problem that the length of the instance input parameter is greater than the length of the return parameter will be thrown.

details as follows:

? select statement

<select id="getUser" resultType="org.apache.ibatis.test.domain.UserDO" >
    // There are three query parameters
  select id,username,password
  from m3_user
  where username = #{username}
</select>

do object

@Builder
public class UserDO extends BaseDO {<!-- -->
  
  private static final long serialVersionUID = 1643909999676303799L;

  private String username;

  private String password;

  private String name;

  private String tel;

  private String isTel() {<!-- -->
    return this.tel;
  }

  private String isT() {<!-- -->
    return this.tel;
  }
  
}

The full-parameter construction method takes four parameters.

//Create an object using the parameterized constructor
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {<!-- -->
  boolean foundValues = false;
   // Loop to construct parameter collection
  for (int i = 0; i < constructor.getParameterTypes().length; i + + ) {<!-- -->
    Class<?> parameterType = constructor.getParameterTypes()[i];
     //According to index i, obtain the data fields of the table below the corresponding array from the ResultSet. The length of the array is the length of the select return field. If the length of the constructor parameter is greater than the length of the return field array, an array out-of-bounds java.lang.IndexOutOfBoundsException will occur.
    String columnName = rsw.getColumnNames().get(i);
      // If the first field returned by the select statement is string, but the first parameter entered by the constructor is another type, such as long, then a parameter conversion exception java.lang.NumberFormatException will occur:
    TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
    Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
    constructorArgTypes.add(parameterType);
    constructorArgs.add(value);
    foundValues = value != null || foundValues;
  }
  return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}

4. How to avoid this exception

? 1. Write a builder class yourself, and the default no-argument constructor is public

? 2. Do not use lombook-related constructor annotations

? 3. Wrote a default parameter constructor, corresponding to the select attribute in xml plus resultmap, and wrote a resultmap corresponding to the constructor