导火索

最近碰到了一个很诡异的问题,通过mybatis原生的selectList正常查询数据库,结果mybatis报了一个indexOutOfBoundsException的异常,百思不得其解,为啥查询会导致索引越界呢?

主要原因就在于lombok的一个注解@Builder,以及@TableField(exist=false),导致查询后回填数据到实体异常

原因

读了一下mybatis源码,主要起因是DefaultResultSetHandler类中的createResultObject方法,里面有一堆条件判断,主要是最后两个条件判断,当类有默认构造器与没有默认构造器执行的逻辑是不一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 不加@Builder注解会走此逻辑
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
// 加了@Builder后默认构造器被替换为了一个全参的构造器,于是
// 就不会走上面的逻辑了
return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

createByConstructorSignature方法跟进去就能看到真正报错的地方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 = value != null || foundValues;
}
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}

因为rsw.getColumnNames拿到的是实体对应表的字段数量,而constructor.getParameterTypes().length拿到的是实体全字段的数量(其中包括了@TableField(exist=false)修饰的字段),所以导致get(i)的越界而抛出indexOutOfBoundsException的异常

结论

我碰到的是这种情况,但是我在网上并没有找到原因,网文只是告诉你加上无参的构造器就行,而并没有分析源码解释问题,很多时候原因比答案更重要


本文作者:oldmee
本文链接:https://oldmee.github.io/2020/09/06/mybatis-indexOutOfBoundsException/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。