xml内容如下
<select id="XXX" resultType="XXX.XX.AppResubscribeVO">
SELECT app.app_name,
app_id,
spec.spec_name,
...
实体类如下
@Data
@AllArgsConstructor
public class AppResubscribeVO
{
@ApiModelProperty(value = "应用名")
private String appName;
@ApiModelProperty(value = "规格名")
private String specName;
@ApiModelProperty(value = "应用ID")
private Integer appId;
...
如图,xml的字段(app_id,spec_name)和实体的字段(specName,appId)顺序不一致,且两个字段的类型也不一致,分别是String和Integer。此时又加了注解@AllArgsConstructor全参构造函数,则没有默认的无参构造函数。这种情况就会导致映射实体出错。
此问题的核心源码mybatisplus是
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List
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);
}
逻辑很简单,关键有一行metaType.hasDefaultConstructor()
见名知意,如果元数据类型有默认的构造方法,就走objectFactory.create(resultType);
没有并且shouldApplyAutomaticMappings(resultMap, false)
就走
createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
这两个方法这里不具体展开了。简单来说第一个方法按照字段名称映射,允许映射时字段不一致,第二个是按照字段自然顺序映射的,不允许不一致。
实际验证,就是实体类有无参构造方法时走第一个,没有时走第二个。
现在我们来看一下
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
和metaType.hasDefaultConstructor()
具体分步骤如下
创建MetaClass
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
MetaClass构造方法
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
this.reflector = reflectorFactory.findForClass(type);
}
findForClass()调用Reflector构造方法
@Override
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
return reflectorMap.computeIfAbsent(type, Reflector::new);
} else {
return new Reflector(type);
}
}
Reflector构造方法
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
这里有一行关键的addDefaultConstructor(clazz);
看一下
private void addDefaultConstructor(Class<?> clazz) {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
.findAny().ifPresent(constructor -> this.defaultConstructor = constructor);
}
可以看到,就是找到clazz的无参构造方法,并设置为defaultConstructor
而metaType.hasDefaultConstructor()
就是reflector的hasDefaultConstructor()
public boolean hasDefaultConstructor() {
return reflector.hasDefaultConstructor();
}
判断有没有defaultConstructor
public boolean hasDefaultConstructor() {
return defaultConstructor != null;
}
实体类上添加了@AllArgsConstructor,导致实体类没有无参构造方法。所以映射时走的按照字段自然顺序映射。了解了原理,如果想要调换顺序也很简单了。
1.去掉@AllArgsConstructor。
如果不需要@AllArgsConstructor,直接去掉就好了,这样无参构造方法自然就默认有了。
2.手动添加@NoArgsConstructor.
手动添加一个无参构造方法。