记一次使用lombook@Builder类引发的Exception
问题情况
使用mybatis查询数据的时候,抛出 Unsupported conversion from DATETIME to xxx
排查数据库字段发现与数据库映射无误
@TableField(fill = FieldFill.INSERT,exist = false)
private LocalDateTime createTime;
尝试解决
在排查无果后尝试查询条件中去掉 createTime的查询
再次抛出Exception
Cause: java.lang.IndexOutOfBoundsException: Index 9 out of bounds for length 9
最终排查结果
实体类中使用@Builder 注解原因导致
具体原因
DefaultResultSetHandler是MyBatis中处理查询结果的默认实现类,用于将查询结果映射为Java对象。
在MyBatis中,查询结果可以通过ResultMap或者@Results注解来进行映射。当我们执行SQL语句并得到ResultSet对象后,DefaultResultSetHandler会根据该ResultSet、映射规则以及目标类型等信息创建出一个与查询结果相对应的Java对象。
在DefaultResultSetHandler中,createByConstructorSignature是一种通过构造函数来创建参数对象的方法。
/**
* 根据构造函数签名和查询结果创建一个对象。
*
* @param rsw 结果集包装器
* @param resultType 目标类型
* @param constructorArgTypes 构造函数参数类型列表
* @param constructorArgs 构造函数参数列表
* @return 创建的对象
* @throws SQLException 如果发生SQL异常
*/
private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
// 获取目标类型所有声明的构造函数
final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
// 查找默认构造函数
final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
if (defaultConstructor != null) {
// 使用默认构造函数创建对象
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
} else {
// 如果没有默认构造函数,则遍历所有构造函数,并尝试使用类型处理器来匹配实参类型
for (Constructor<?> constructor : constructors) {
if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
// 匹配成功则使用该构造函数创建对象
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);
}
}
}
// 如果没有找到匹配的构造函数,则抛出异常
throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
}
重点在于defaultConsturctor只使用@builder 会生成全参构造, 使用之后会走下面的逻辑
/**
* 根据构造函数和查询结果创建一个对象。
*
* @param rsw 结果集包装器
* @param resultType 目标类型
* @param constructorArgTypes 构造函数参数类型列表
* @param constructorArgs 构造函数参数列表
* @param constructor 构造函数
* @return 创建的对象,如果没有找到任何值则返回null
* @throws SQLException 如果发生SQL异常
*/
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
boolean foundValues = false;
for (int i = 0; i < constructor.getParameterTypes().length; i++) {
// 获取构造函数参数类型
Class<?> parameterType = constructor.getParameterTypes()[i];
// 获取列名
String columnName = rsw.getColumnNames().get(i);
// 获取类型处理器
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
// 获取结果值
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
// 将构造函数参数类型和参数值添加到对应的列表中
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
// 如果找到了非空值,则将foundValues设置为true
foundValues = value != null || foundValues;
}
// 如果没有找到任何值,则返回null
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
在这段逻辑中构造器的参数和我们自定义查询中的列是一一对应的 ,但是我们自定义查询的时候可能只会查询我们所需要的几个字段,不会查询entity中的所有字段,这就导致字段对应不起来,抛出各种异常