一 报错问题
问题描述:原实体是没带@Builder注解的,所以查询也是正常的,但是,后面突然接口报错了。报错信息如下:
1 | org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: |
二 报错原因定位与分析
报错中明确了是
IndexOutOfBoundsException
异常,而且是结果集处理的时候产生的。
可是我定义的实体,明明是没加构造即默认有一个无参构造,而且mybatis也不可能会判断实体的属性数量是否和查询的数据库字段数量一致的(看过源码都知道这点,即使没看过源码,大家用过mybatis应该都知道,有时候我们不想返回太多属性,但是用实体包装返回也是不会出现报错问题的。
既然如此,我就去查看了一下实体,实体的变更记录里,什么都没变,就是被某同学加了一个Lombok
的@Builder
注解。
看到这里,也就明白了,因为@Builder
注解会帮你编译出一个全参构造,即无参构造就不存在了。而mybatis里使用实体构造反射,如果构造有参数,默认会给所有参数去设置值,设置的值就是从查询出来的字段中来,如果字段的数量没有构造函数的参数多,就会引发以上的报错。
mybatis相关源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171// DefaultResultSetHandler
/**
* 创建结果对象
* @param rsw 结果集包装器
* @param resultMap 结果集
* @param constructorArgTypes 构造函数的属性类型列表,创建对象时使用
* @param constructorArgs 构造函数的属性列表,创建对象时使用
* @param columnPrefix
* @return
* @throws SQLException
*/
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, resultType, constructorArgTypes, constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
/**
* 通过结果集映射构造结果对象
* @param rsw 结果集包装器
* @param resultType 结果类型
* @param constructorArgTypes 构造函数的属性类型列表,创建对象时使用
* @param constructorArgs 构造函数的属性列表,创建对象时使用
* @param constructorArgs
* @param columnPrefix
* @return
*/
Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
boolean foundValues = false;
// 遍历结果集映射
for (ResultMapping constructorMapping : constructorMappings) {
// 获取属性类型
final Class<?> parameterType = constructorMapping.getJavaType();
// 获取字段名
final String column = constructorMapping.getColumn();
final Object value;
try {
if (constructorMapping.getNestedQueryId() != null) { // 拥有嵌套查询
// 获取嵌套查询构造函数值
value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
} else if (constructorMapping.getNestedResultMapId() != null) { // 嵌套查询拥有结果集映射
final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
} else {
// 获取对应的类型处理器
final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
// 通过类型处理器获取到值
value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
}
} catch (ResultMapException | SQLException e) {
throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
}
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
/**
* 通过结果类型的构造函数创建结果对象
* @param rsw 结果集包装器
* @param resultType 结果类型
* @param constructorArgTypes 构造函数的属性类型列表,创建对象时使用
* @param constructorArgs 构造函数的属性列表,创建对象时使用
* @return
* @throws SQLException
*/
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());
}
/**
* 使用构造器创建对象
* @param rsw 结果集包装器
* @param resultType 结果类型
* @param constructorArgTypes 构造函数的属性类型列表,创建对象时使用
* @param constructorArgs 构造函数的属性列表,创建对象时使用
* @param constructor 构造器
* @return
* @throws SQLException
*/
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];
// 获取第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;
}
// 全部属性值都为null时直接返回null对象
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
/**
* 获取默认的构造器
* @param constructors
* @return
*/
private Constructor<?> findDefaultConstructor(final Constructor<?>[] constructors) {
// 构造函数只有一个则直接返回
if (constructors.length == 1) {
return constructors[0];
}
for (final Constructor<?> constructor : constructors) {
// 如果构造函数带有 @AutomapConstructor 注解默认使用该构造
if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
return constructor;
}
}
// 多个构造函数时,且没有构造函数包含 @AutomapConstructor 注解时 直接返回null
return null;
}
/**
* 判断是否允许构造器使用类型处理器
* @param constructor
* @param jdbcTypes
* @return
*/
private boolean allowedConstructorUsingTypeHandlers(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
final Class<?>[] parameterTypes = constructor.getParameterTypes();
//属性数量与查询字段的数量不一致 返回false
if (parameterTypes.length != jdbcTypes.size()) {
return false;
}
// 遍历每个属性 看每个属性与对应下标的字段是否匹配类型处理器
for (int i = 0; i < parameterTypes.length; i++) {
// 如果不匹配类型处理器 则返回false
if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
return false;
}
}
return true;
}
通过以上的源码分析,结果对象没有类型处理器、没有无参构造、不是接口类型、或者mapper没有配置resultMap结果集映射,都会使用自动映射,把数据库的字段映射到实体上。而一旦查询的字段与实体的构造入参不匹配则会直接报错。
解决方案
去掉@Builder注解,把使用了该实体Builder的地方调整一下,如果非要使用链式方式设置属性值,可以使用@Accessors(chain = true)注解替换。
三 总结
我们在定义数据库实体的时候,最好保留无参构造。
Lombok注解大大方便了我们,精简了我们的代码,但是加注解的时候,要想想是否合适,会不会造成一些其他问题,所以这就需要我们非常了解注解给我们自动做了什么事情,避免引发一些其他问题。