mybatis字段映射的容错性
起因从一个bug说起,大致经历如下:
经过一次selectById,然后用查出来的数据做updateById。
数据的值 由 '' --> 0;
找遍相关代码,没有发现什么地方有setType(0)的动作,然后怀疑mybatis查询时的字段映射做了容错处理,将''转换为Integer的0。
复现:造一条数据,数据库值为'',然后selectById,发现得到的type确实是0;
为了验证猜想,对跟踪了mybatis的相关源码。
mybatis在查询完成数据库后,需要进行字段映射。
mybatis字段映射的任务有:
- 数据库属性与java属性的映射
- 数据库类型与java类型的转换
类型的自动转换里有一些猫腻,在做类型转换的时候,如果遇到困难(类型不匹配),可以选择抛异常,也可以选择容错。而mybatis选择的是容错。容错是一中缺乏严谨性的策略。
从一个例子说起字段映射的容错性
java类型private Integer type;
mysql类型type` char(1) DEFAULT '' COMMENT '类型 (1:KA 和 2:PA)',
mybatis处理字段映射的逻辑主要是在DefaultResultSetHandlerorg.apache.ibatis.executor.resultset.DefaultResultSetHandler
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);
}
}
根据不同的类型,进入不同的TypeHandler.
mybatis提供的Handler有如下几种。
如Integet类型就被IntegerTypeHandler
此处用的方法如下,ResultSet.getInt
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
int result = rs.getInt(columnName);
代码跟踪到此,可发现mysql查询的结果为空字符串,也就是 '\u0000',和数据库值是一致的。
最后围绕在一个复杂的三目运算符
public int resultSet_getInt(ResultSetProxy rs, String columnLabel) throws SQLException {
return this.pos < this.filterSize ? this.nextFilter().resultSet_getInt(this, rs, columnLabel) : rs.getResultSetRaw().getInt(columnLabel);
}
最终在一个getInt的方法里找到了答案,默认赋0;
此处可以得到结论,mybatis的字段映射时,会有少量的容错处理,将数据库的CHAR类型的 空字符串 转换Integer做了容错,没有抛异常,也没有返回null,选择的是int的默认值0。
同理如果其它类型,也会有相关的默认值容错,如getDouble的0.0;getBigDecimal的new BigDecimal(0);...
根本性的避免此类问题的发生,还需数据库字段类型和java的类型尽量保持一致。