这里面非常重要的一个方法applyPropertyMappings
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it column = null; } if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); // issue #541 make property optional final String property = propertyMapping.getProperty(); // issue #377, call setter on nulls if (value != DEFERED && property != null && (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()))) { metaObject.setValue(property, value); } if (property != null && (value != null || value == DEFERED)) { foundValues = true; } } } return foundValues; }
其实在这里可以窥视到从数据表的列如何映射到对象的属性的一点端倪了:
先把resultMap中取得的列名转换为大写字母,再截取它的前缀(去除特殊字符),把这个前缀和要映射到的对象的属性进行比对,符合的就映射过去,即对POJO对象注入对应属性值。所以这里是不受到字母大小写的影响的
从此处需要注意了,for循环里已经按照数据库表列为维度,一个一个的处理了。这里面有一行代码必要详细看一下:
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
还记得我们最前面说的Id被赋值为BigInteger了吗?所以我猜测就是这里的value值自己本身就是BigInteger的类型了,看看getPropertyMappingValue咋写的:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } else if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK? return DEFERED; } else { final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); return typeHandler.getResult(rs, column); } }
到了这一步其实我就比较更为熟悉了。
调试看到了这个,思路就越来越清晰了。很显然,就是处理转换的类型转换器竟然是UnKonownTypeHandler,所以给我们转换成了什么鬼呢?为了一探究竟我们跟踪到它的getNullableResult方法:
@Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { TypeHandler<?> handler = resolveTypeHandler(rs, columnName); return handler.getResult(rs, columnName); } private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) { try { Map<String,Integer> columnIndexLookup; columnIndexLookup = new HashMap<String,Integer>(); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); for (int i=1; i <= count; i++) { String name = rsmd.getColumnName(i); columnIndexLookup.put(name,i); } Integer columnIndex = columnIndexLookup.get(column); TypeHandler<?> handler = null; if (columnIndex != null) { handler = resolveTypeHandler(rsmd, columnIndex); } if (handler == null || handler instanceof UnknownTypeHandler) { handler = OBJECT_TYPE_HANDLER; } return handler; } catch (SQLException e) { throw new TypeException("Error determining JDBC type for column " + column + ". Cause: " + e, e); } }
看到问题的又一根源了,MyBatis完全根据数据库中id字段的类型来推断Java类型,而这种推断又依赖于这部分代码
这是非常不好的一种处理方式,因为Map里面的值竟然采用自然排序,然后通过index去识别,显然就非常有问题。
所以我们看到的现象是,有的有问题,有的没有问题。有的问题的方式并且都不尽相同,有的成了Long,有的成了BigInteger
我个人认为这是MyBatis设计另一个很失败的地方,可以定义为一个bug级别的存在。关键它还是“软病”,让我着实花了好久找到此处。后续希望自己可以提个issue被采纳
那我们看到了此处被选中的为BigInteger的转换器,所以自然而然得到的值类型如下:
所以,最直接的问题,我们只剩下一个了,为何BigInteger类型的值,可以被set到Integer类型的Id上面。那就继续跟踪这句代码:
metaObject.setValue(property, value);
setValue方法如下:
public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null && prop.getChildren() != null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value); } else { objectWrapper.set(prop, value); } }
执行了objectWrapper.set(prop, value);这一句,调用的objectWrapper的方法。这里其实会引申出好几个问题,objectWrapper怎么来的?暂时不讨论这快,接着往下走吧。调试发现objectWrapper其实是个BeanWrapper,所以我们点进去看看它的set方法:
@Override public void set(PropertyTokenizer prop, Object value) { if (prop.getIndex() != null) { Object collection = resolveCollection(prop, object); setCollectionValue(prop, collection, value); } else { setBeanProperty(prop, object, value); } } //主要执行的方法 private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) { try { Invoker method = metaClass.getSetInvoker(prop.getName()); Object[] params = {value}; try { method.invoke(object, params); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } catch (Throwable t) { throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t); } }
这里面重点就来了,关键就在于metaClass.getSetInvoker(prop.getName()); 中的这个metaClass属性,它其实就是我上面说到的元信息的概念(该理念在流行框架的设计中经常用的),它包含有set方法的信息: