MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详细说明

作者:袖梨 2026-08-08

MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详细说明并不只看表面做法,关键还要理解相关条件、限制和后续影响。

问题现象

最近遇到一个问题,当 mybatis 的 mapper.xml 文件中使用来映射的对象使用了 lombok 中的 @Builder 注解修饰后,发生了字段值映射错位的问题,而当去掉 @Builder 注解之后字段映射值又正常了。

问题复现

为了复现这个问题,搞了一个 demo 代码。首先定义了一个 user 表,假设它里面只有 id, name, update_time 这三个字段,其中 namevarchar 类型, update_timetimestamp 类型。表的定义如下:

CREATE TABLE user (      id BIGINT(20) NOT NULL AUTO_INCREMENT,      name VARCHAR(30) NULL DEFAULT NULL ,      update_time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,      PRIMARY KEY (id)  );

然后定义了一个 User 类,它里面定义了 updateTime, name 字段,且 updateTime 字段的顺序在 name 注解前面,同时使用了 lombok 中的 @Builder 注解修饰。代码如下:

@Builder  @Data  public class User {      private Date updateTime;      private String name;  }

然后 mapper.xml 文件中定义了一个查询所有用户的语句,在这个语句中 select 的时候,name 字段的顺序在 update_time 前面,这点很重要,使用的 MyBatis 版本是 3.5.10。Xml 文件定义如下:

<mapper namespace="com.mapper.UserMapper">      <resultMap id="userMap" type="com.entity.User">      <result property="name" column="name"/>      <result property="updateTime" column="update_time"/>  </resultMap>  <select id="listAll" resultMap="userMap">      select name, update_time      from user  </select>  </mapper>

在数据库初始化的时候往 user 表里面插入了一条数据。SQL 如下:

INSERT INTO user (id, name) VALUES (1, 'Jone');

执行查询后接口直接报错了,从报错看是尝试把 Jone 这个字符串转为 Timestamp 类型从而报错了,实际报错是在 DateTyperHanldergetNullableResult() 方法里面触发的。如下图所示:

MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详解

MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详解

而当把 @Builder 注解去掉之后使用 @Datae 注解修饰,再次执行查询,接口返回的结果就正常了。如下图所示:

@Data  public class User {      private Date updateTime;      private String name;  }

MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详解

为啥使用了 @Builder 就会报错,去掉之后就正常了,接下来将从源码角度来分析一下。

问题原因

在 MyBatis 中是通过 ResultSetHandler 来对查询结果进行处理的。首先它会通过反射的方式创建一个对象的实例,在使用 @Builder 注解和不使用注解的区别对于 User 类来说是 @Builder 注解会生成一个全参的构造函数,而不使用的情况下,则会有一个默认的无参构造函数。如下图所示:

MyBatis映射值报错的罪魁祸首竟然是Lombok的@Builder详解

在 MyBatis 中的 DefaultResultSetHandlercreateResultObject() 方法实现了通过反射来创建对象,当是全参构造函数时走的的是 createByConstructorSignature() 方法这个分支。代码如下:

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,     String columnPrefix)    throws SQLException {    final Class<?> resultType = resultMap.getType();    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();    if (hasTypeHandlerForResultObject(rsw, resultType)) {        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);    } else if (!constructorMappings.isEmpty()) {        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { // 如果有无参构造函数直接走这里就创建对象了        return objectFactory.create(resultType);    } else if (shouldApplyAutomaticMappings(resultMap, false)) {        return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs);    }    throw new ExecutorException("Do not know how to create an instance of " + resultType);}

createByConstructorSignature() 方法实现了通过构造函数的方式来反射创建对象。代码如下:

private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType,    List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {    return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,        findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));}

在创建之前会通过 findConstructorForAutomapping() 方法来查找一个合适的构造函数,当类的构造函数只有一个时,会直接使用这个这个构造函数。代码如下:

private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {    Constructor<?>[] constructors = resultType.getDeclaredConstructors();    // 只有一个构造函数时,直接使用这个构造函数    if (constructors.length == 1) {        return Optional.of(constructors[0]);    }    for (final Constructor<?> constructor : constructors) {        if (constructor.isAnnotationPresent(AutomapConstructor.class)) {            return Optional.of(constructor);        }    }    if (configuration.isArgNameBasedConstructorAutoMapping()) {      // Finding-best-match type implementation is possible,      // but using @AutomapConstructor seems sufficient.        throw new ExecutorException(MessageFormat.format(          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",          resultType.getName()));    } else {        return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();    }}

然后在 applyConstructorAutomapping() 方法中会调用 applyColumnOrderBasedConstructorAutomapping() 方法。从该方法的命名也可以看出,它是实现了根据下标,而不是参数名称来实现构造参数的映射。代码如下:

private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {    boolean foundValues = false;    if (configuration.isArgNameBasedConstructorAutoMapping()) {      // 这个是根据构造参数的名称映射        foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,          constructor, foundValues);    } else {      // 这个是根据构造参数的顺序映射        foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,          foundValues);    }    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;  }

applyColumnOrderBasedConstructorAutomapping() 方法中会从下标取构造函数中的参数,同时根据下标取查询结果里面的列名,先调用 rsw.getTypeHandler() 找到对应的 TypeHandler ,然后调用 TypeHandler 的 getResult() 方法尝试将列对应的值转为对应的构造函数的参数类型。代码如下:

private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {    for (int i = 0; i < constructor.getParameterTypes().length; i++) {        Class<?> parameterType = constructor.getParameterTypes()[i];        String columnName = rsw.getColumnNames().get(i);        // 先找到对应的TypeHandler        TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);        // 调用TypeHandler将列对应的值转为对应的构造参数类型        Object value = typeHandler.getResult(rsw.getResultSet(), columnName);        constructorArgTypes.add(parameterType);        constructorArgs.add(value);        foundValues = value != null || foundValues;    }    return foundValues;}

在本案例中构造函数的第一个参数类型是 Date 类型,第一个列名是 name。首先根据列名是找不到对应的 TypeHandler 的,然后调用 TypeHandlerRegistrygetTypeHandler() 方法获取对应的 TypeHandler

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {    TypeHandler<?> handler = null;    // 根据列名找不到对应的TypeHandler    Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);    if (columnHandlers == null) {        columnHandlers = new HashMap<>();        typeHandlerMap.put(columnName, columnHandlers);    } else {        handler = columnHandlers.get(propertyType);    }    if (handler == null) {        JdbcType jdbcType = getJdbcType(columnName);        handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);            // 省略代码    }    return handler;}

在这个方法里面会先尝试根据 jdbcType 作为 key 获取对应的 TypeHandler,但是获取不到,然后尝试根据 null 作为 key 获取对应的 TypeHandler,这个实际上是获取到的是 DateTypeHandler 这个对象的实例。代码如下:

private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {    if (ParamMap.class.equals(type)) {      return null;    }    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);    TypeHandler<?> handler = null;    if (jdbcHandlerMap != null) {      // 先根据jdbcType作为key获取对应的TypeHandler        handler = jdbcHandlerMap.get(jdbcType);        if (handler == null) {            // 根据null作为key获取,这里会获取到DateTypeHandler            handler = jdbcHandlerMap.get(null);        }        if (handler == null) {            // #591            handler = pickSoleHandler(jdbcHandlerMap);        }    }    // type drives generics here    return (TypeHandler<T>) handler;}

DateTypeHandler 则是在 TypeHandlerRegistry 的构造函数里面注册的,注册的时候恰好是 null 作为 key 的。代码如下:

public TypeHandlerRegistry(Configuration configuration) {    this.unknownTypeHandler = new UnknownTypeHandler(configuration);    // 省略其它代码    register(Date.class, new DateTypeHandler());    register(JdbcType.TIMESTAMP, new DateTypeHandler());}private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);    if (mappedJdbcTypes != null) {        for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {            register(javaType, handledJdbcType, typeHandler);        }        if (mappedJdbcTypes.includeNullJdbcType()) {            register(javaType, null, typeHandler);        }    } else {        // 这里注册了key为null的DateTypeHandler        register(javaType, null, typeHandler);    }}

在本案例中 DateTypeHandlergetNullableResult() 方法中传入的列名是 name,然后调用 ResultSetgetTimestamp() 方法获取 Timestamp 类型的实例,因为实际上这个列的值是 Jone ,当然是转不成 Timestamp 类型的,因此报错了。代码如下:

public Date getNullableResult(ResultSet rs, String columnName)      throws SQLException {    Timestamp sqlTimestamp = rs.getTimestamp(columnName);    if (sqlTimestamp != null) {        return new Date(sqlTimestamp.getTime());    }    return null;}

当去掉 @Builder 注解时,使用的构造函数就是无参构造函数,直接通过无参构造函数就创建对象了,自然就不需要按照构造参数下标将列转为对应的构造参数类型了。代码如下:

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)    throws SQLException {    final Class<?> resultType = resultMap.getType();    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();    if (hasTypeHandlerForResultObject(rsw, resultType)) {        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);    } else if (!constructorMappings.isEmpty()) {        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { // 如果有无参构造函数直接走这里就创建对象了        return objectFactory.create(resultType);    } else if (shouldApplyAutomaticMappings(resultMap, false)) {        return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs);    }    throw new ExecutorException("Do not know how to create an instance of " + resultType);}

总结

相关文章

精彩推荐