结论
mybatis组装集合属性的时候,会先调用属性的get方法,如果该属性不为null,则不会调用set方法设值,造成数据丢失。正常的属性不会,仅限集合
问题开始的地方
有一个DTO的属性是数组,下游有个新系统需要该值为非null,我这里临时修改了get方法,判断如果为null的话,返回new ArrayList();
public List<PluginVersion> getVersionList() {
return versionList == null ? new ArrayList() : versionList;
}
问题解决了,但是接着就发现所有查询出来的该字段数据都变成了空数组
原因
先查了一下sql,能看到有一些是有值的,所以不是sql查询的问题,那就是mybatis组装的问题
正常来讲,改写属性的默认值或者get值,并不会影响mybatis塞值,之前也这么用过,但是后续跟进却发现集合属性是不一样的
package org.apache.ibatis.executor.resultset;
public class DefaultResultSetHandler implements ResultSetHandler {
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
String resultMapId = resultMap.getId();
Object rowValue = partialObject;
if (partialObject != null) {
MetaObject metaObject = this.configuration.newMetaObject(partialObject);
this.putAncestor(partialObject, resultMapId, columnPrefix);
this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
this.ancestorObjects.remove(resultMapId);
} else {
ResultLoaderMap lazyLoader = new ResultLoaderMap();
rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
MetaObject metaObject = this.configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (this.shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
//这一行是塞普通属性的值
foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
this.putAncestor(rowValue, resultMapId, columnPrefix);
//这一行是塞集合属性的值
foundValues = this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
this.ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
this.nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
}
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
...
CacheKey combinedKey = this.combineKeys(rowKey, parentRowKey);
Object rowValue = this.nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null;
//塞值
this.instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (this.anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
rowValue = this.getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
...
}
private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
String propertyName = resultMapping.getProperty();
Object propertyValue = metaObject.getValue(propertyName);
if (propertyValue == null) {
Class<?> type = resultMapping.getJavaType();
//只有为null的情况下才会塞值
if (type == null) {
type = metaObject.getSetterType(propertyName);
}
try {
if (this.objectFactory.isCollection(type)) {
propertyValue = this.objectFactory.create(type);
metaObject.setValue(propertyName, propertyValue);
return propertyValue;
}
} catch (Exception var7) {
throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + var7, var7);
}
} else if (this.objectFactory.isCollection(propertyValue.getClass())) {
return propertyValue;
}
return null;
}
注释上已经很清楚了,集合类的属性会判断是否已经有值,如果没有的话才会塞值,如果已经有默认的非null值,那么就不会调用get方法去塞值
所以我们在改写get、set方法的时候需要格外注意,现在的建议是不要再去改这两个默认方法了