1、现象
mybatis版本从3.4.2升级到3.5.6之后,碰到一个问题select 值为空的问题,在3.4.2的版本下可以正常筛选出来。对应的mapper.xml文件如下所示。 结果digtAccArId字段为空,crdtfwdfacepicfilerte不为空。
<?xml vsersion="1.0" encoding="UTF-8"?>
<sqlMap namespace="xxxx">
<resultMap id = "baseResult" type="com.test.info.testPO">
<result column ="Digt_Acc_Ar_ID" property = "digtAccArId" />
<result column ="CrdtFwdFacePicFileRte" property = "crdtfwdfacepicfilerte" />
</resultMap>
<sql id = "base_list">
Digt_Acc_Ar_ID as digtAccArId,
CrdtFwdFacePicFileRte as crdtfwdfacepicfilerte
</sql>
</sqlMap>2、概要分析
首先列举一下mybaits的数据库列和java的PO字段的映射方式:
通过select ... as的这种加别名的方式映射
通过resultMap的方式映射
通过数据库表列明与PO字段名字相似的方式,并开启mapUnderscoreToCamelCase
下面通过举例说明
com.test.info.po的结构如下
public Class TestPO {
private String digtAccArId;
private String crdtfwdfacepicfilerte;
}数据库表结构如下
create table test
(
Digt_Acc_Ar_ID varchar(23),
CrdtFwdFacePicFileRte varchar(10)
)方式一、通过select ... as的这种加别名的方式映射,as后的字段是和PO的属性一致
<sql id = "base_list">
Digt_Acc_Ar_ID as digtAccArId,
CrdtFwdFacePicFileRte as crdtfwdfacepicfilerte
</sql>方式二、通过resultMap的方式映射 column是数据库字段列名,property是PO的属性名
<resultMap id = "baseResult" type="com.test.info.testPO">
<result column ="Digt_Acc_Ar_ID" property = "digtAccArId" />
<result column ="CrdtFwdFacePicFileRte" property = "crdtfwdfacepicfilerte" />
</resultMap>方式三、保持数据库列字段名和PO的名字类似(解析的时候,大小写不敏感,开启mapUnderscoreToCamelCase,会把“_”去掉)
<sql id = "base_list">
Digt_Acc_Ar_ID,
CrdtFwdFacePicFileRte
</sql>上述mapper.xml中同时写了两种,理论上来说select出来的数据映射不上是合乎情理的。然而事实在3.4.2的版本又是可以达到select的效果的。此时,我们只能从源码出发,一探究竟。
3、源码分析
3.1 、DefaultResultSetHandler
从现象上来说,问题应该出在数据映射上,目光直接聚焦到DefaultResultSetHandler。 可以看到在handleRowValues这个方法里面会根据是否存在resultMap来做不同映射处理。
/**
HANDLE ROWS FOR SIMPLE RESULTMAP
*/
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}在handleRowValuesForNestedResultMap中会调用getRowValue。 在getRowValue中applyAutomaticMappings和applyPropertyMappings
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
MetaObject metaObject = this.configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
//重点关注主要方法
foundValues = (applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues);
}
//重点关注主要方法
foundValues = (applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues);
foundValues = (lazyLoader.size() > 0 || foundValues);
rowValue = (foundValues || this.configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
}
return rowValue;
}applyAutomaticMappings 中一个重要的步骤就是创建mapping映射对。UnMappedColumnAutoMapping存放在resultSet中但是没有在resultMap中的那些字段。(resultSet 中用select出来的字段和 resultMap中的column)
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
String mapKey = resultMap.getId() + ":" + columnPrefix;
List<UnMappedColumnAutoMapping> autoMapping = this.autoMappingsCache.get(mapKey);
if (autoMapping == null) {
autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
//获取不在resultMap中那些字段()
List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if (columnPrefix != null && !columnPrefix.isEmpty())
{
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
String property = metaObject.findProperty(propertyName, this.configuration.isMapUnderscoreToCamelCase());
if (property != null && metaObject.hasSetter(property)) {
//这里是3.4.4开始增加的,做了一个resultMap和默认处理
if (resultMap.getMappedProperties().contains(property)) {
continue;
}
Class<?> propertyType = metaObject.getSetterType(property);
if (this.typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive())); continue;
}
this.configuration.getAutoMappingUnknownColumnBehavior()
.doAction(this.mappedStatement, columnName, property, propertyType);
continue;
}
this.configuration.getAutoMappingUnknownColumnBehavior()
.doAction(this.mappedStatement, columnName, (property != null) ? property : propertyName, null);
}
this.autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}默认取映射关系的时候,会根据useCamelCaseMapping把下划线去掉。该配置在mybatis-config.xml中由mapUnderscoreToCamelCase指定(参考:https://mybatis.org/mybatis-3/zh/configuration.html)
public String findProperty(String name, boolean useCamelCaseMapping) {
if (useCamelCaseMapping) {
name = name.replace("_", "");
}
return findProperty(name);
}在3.4.4版本开始增加了一段,正式这一段的加入,导致了3.4.2 与 3.4.4之间的取值差异
if (resultMap.getMappedProperties().contains(property)) {
continue;
} //这里是3.4.4开始增加的,做了一个resultMap和默认处理3.2 、举例说明
针对问题中的sqlmap,在经过查询之后再resultSet的结果中会存在类似一下的结果:“digtAccArId”:“1234567”, “crdtfwdfacepicfilerte”:“1234” (具体的结果不尽相同,粗略表达,大小写不敏感)
在3.4.2的版本中处理之后的映射关系应该是:“digtAccArId”:“1234567”, “crdtfwdfacepicfilerte”:“1234”, “Digt_Acc_Ar_ID” “CrdtFwdFacePicFileRte”
在3.4.4的版本中处理之后的映射关系应该是: “Digt_Acc_Ar_ID” :“ ”“CrdtFwdFacePicFileRte”:“”
所以导致了前述的问题。
4、总结
Mybatis版本3.4.2升级到3.4.4 修改了默认映射取值的规则,从之前无论是否存在resultMap映射都会做一个默认处理,改为了仅针对不存在的resultMap的字段做默认处理
默认处理中的会把筛选结果的字段中“_”去掉,大小写不敏感。所以如果数据库中存在类似abc和ab_c这样的两个字段会有问题