MyBatis查询结果集映射到JavaBean原理浅谈

MyBatis对JDBC做了很好的封装,其中一个吸引人的地方就是能够对从数据库内查询出来的表的记录集映射生成一系列JavaBean,供应用程序使用。今天跟着源码一层一层探讨一下MyBatis把数据库记录集映射到POJO对象的一个简要的过程。

1. DefaultResultSetHandler类

处理结果集的主要实现类,先从这个方法看

[java]  view plain  copy
  1. private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {  
  2.         try {  
  3.             if(parentMapping != null) {  
  4.                 this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);  
  5.             } else if(this.resultHandler == null) {  
  6.                 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);  
  7.                 this.<span style="color:#ff0000;">handleRowValues</span>(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);  
  8.                 multipleResults.add(defaultResultHandler.getResultList());  
  9.             } else {  
  10.                 this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);  
  11.             }  
  12.         } finally {  
  13.             this.closeResultSet(rsw.getResultSet());  
  14.      }  
  15.   
  16. }  
它有这样一些类型的参数:ResultSetMapper、ResultMap、一个List、ResultMapping,上面的的代码主要是用到了这个类自己的方法handleRowValues,并需要一个DefaulResultHandler的对象, handleRowValues方法把处理后的结果列表添加到List<object>内,可以得出一个初步结论:

不管方法handleRowValues里面调用的层次多深,最终把结果集ResultSet经过处理,得到了需要的那些POJO对象并存储到一个List里面。

2. 来看一下方法handleRowValues方法:

[java]  view plain  copy
  1. public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {  
  2.         if(resultMap.hasNestedResultMaps()) {  
  3.             this.ensureNoRowBounds();  
  4.             this.checkResultHandler();  
  5.             this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);  
  6.         } else {  
  7.             this.<span style="color:#ff0000;">handleRowValuesForSimpleResultMap</span>(rsw, resultMap, resultHandler, rowBounds, parentMapping);  
  8.         }  
  9.   
  10. }  
又多了一个RowBounds类型的参数,点进去看一下,封装了limit和offset的一个类,应该是拿来传入limit和offset参数用的,方法中分两种情况分别调用了两个方法,前一种是resultMap中有嵌套,后一种没有嵌套,这里重点看看后一种方法,ResultMap是包装了数据表列和对象属性等信息的一个类。

[java]  view plain  copy
  1. private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)  
  2.       throws SQLException {  
  3.     DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();  
  4.     skipRows(rsw.getResultSet(), rowBounds);  
  5.     while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {  
  6.       ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);  
  7.       Object rowValue =<span style="color:#ff0000;"> getRowValue</span>(rsw, discriminatedResultMap);  
  8.       storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());  
  9.     }  
  10. }  
又来一个陌生的类型DefaultResultContext,打开看看,并不复杂,这个类封装了结果集个数和当前结果,其中的方法skipRows点过去看看:

[java]  view plain  copy
  1. private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {  
  2.     if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {  
  3.       if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {  
  4.         rs.absolute(rowBounds.getOffset());  
  5.       }  
  6.     } else {  
  7.       for (int i = 0; i < rowBounds.getOffset(); i++) {  
  8.         rs.next();  
  9.       }  
  10.     }  
  11.  }  
这里不难理解了,它用到了rowBounds参数,根据rowBounds给出的offset移动结果集的起始游标。再回到上一个方法,其中最重要的方法getRowValue:

[java]  view plain  copy
  1. private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {  
  2.     final ResultLoaderMap lazyLoader = new ResultLoaderMap();  
  3.     Object resultObject = <span style="color:#ff0000;">createResultObject</span>(rsw, resultMap, lazyLoader, null);  
  4.     if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {  
  5.       final MetaObject <span style="color:#ff0000;">metaObject</span> = configuration.newMetaObject(resultObject);  
  6.       boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();  
  7.       if (shouldApplyAutomaticMappings(resultMap, false)) {  
  8.         foundValues = <span style="color:#ff0000;">applyAutomaticMappings</span>(rsw, resultMap, metaObject, null) || foundValues;  
  9.       }  
  10.       foundValues = <span style="color:#ff0000;">applyPropertyMappings</span>(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;  
  11.       foundValues = lazyLoader.size() > 0 || foundValues;  
  12.       resultObject = foundValues ? resultObject : null;  
  13.       return resultObject;  
  14.     }  
  15.     return resultObject;  
  16.  }  

3. 注意上述方法的第三行,进一步打开createResultObject方法看看:

[java]  view plain  copy
  1. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {  
  2.     final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();  
  3.     final List<Object> constructorArgs = new ArrayList<Object>();  
  4.     final Object resultObject = <span style="color:#ff0000;">createResultObject</span>(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);  
  5.     if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {  
  6.       final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();  
  7.       for (ResultMapping propertyMapping : propertyMappings) {  
  8.         // issue gcode #109 && issue #149  
  9.         if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {  
  10.           return <span style="color:#ff0000;">configuration.getProxyFactory().createProxy</span>(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);  
  11.         }  
  12.       }  
  13.     }  
  14.     return resultObject;  
  15.   }  
其中调用了它的一个重载方法如下:

[java]  view plain  copy
  1. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)  
  2.       throws SQLException {  
  3.     final Class<?> resultType = resultMap.getType();  
  4.     final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);  
  5.     final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();  
  6.     if (hasTypeHandlerForResultObject(rsw, resultType)) {  
  7.       return createPrimitiveResultObject(rsw, resultMap, columnPrefix);  
  8.     } else if (!constructorMappings.isEmpty()) {  
  9.       return <span style="color:#ff0000;">createParameterizedResultObject</span>(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);  
  10.     } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {  
  11.       return objectFactory.create(resultType);  
  12.     } else if (shouldApplyAutomaticMappings(resultMap, false)) {  
  13.       return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);  
  14.     }  
  15.     throw new ExecutorException("Do not know how to create an instance of " + resultType);  
  16.  }  
分几种不同情况调用了不同的创建实体类对象的方法,主要看看下面这个:

[java]  view plain  copy
  1. Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,  
  2.       List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {  
  3.     boolean foundValues = false;  
  4.     for (ResultMapping constructorMapping : constructorMappings) {  
  5.       final Class<?> parameterType = constructorMapping.getJavaType();  
  6.       final String column = constructorMapping.getColumn();  
  7.       final Object value;  
  8.       try {  
  9.         if (constructorMapping.getNestedQueryId() != null) {  
  10.           value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);  
  11.         } else if (constructorMapping.getNestedResultMapId() != null) {  
  12.           final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());  
  13.           value = getRowValue(rsw, resultMap);  
  14.         } else {  
  15.           final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();  
  16.           value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));  
  17.         }  
  18.       } catch (ResultMapException e) {  
  19.         throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);  
  20.       } catch (SQLException e) {  
  21.         throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);  
  22.       }  
  23.       constructorArgTypes.add(parameterType);  
  24.       constructorArgs.add(value);  
  25.       foundValues = value != null || foundValues;  
  26.     }  
  27.     return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;  
  28.   }  
方法通过column解析了一个value,如果在配置中能找到可以映射的属性,那么最后就创建一个POJO对象。再返回到3处的createResultObject,它间接使用了代理工厂,把上述方法返回的对象传给代理工厂的一个代理对象,又进步不把结果传给上一层。 再回到getRowValue方法,其中的metaObject对象使用了上面返回的结果对象做了进一步封装,getRowValue有两个重要的方法:

[java]  view plain  copy
  1. private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {  
  2.     List<UnMappedColumnAutoMapping> autoMapping = <span style="color:#ff0000;">createAutomaticMappings</span>(rsw, resultMap, metaObject, columnPrefix);  
  3.     boolean foundValues = false;  
  4.     if (autoMapping.size() > 0) {  
  5.       for (UnMappedColumnAutoMapping mapping : autoMapping) {  
  6.         final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);  
  7.         // issue #377, call setter on nulls  
  8.         if (value != null || configuration.isCallSettersOnNulls()) {  
  9.           if (value != null || !mapping.primitive) {  
  10.             metaObject.setValue(mapping.property, value);  
  11.           }  
  12.           foundValues = true;  
  13.         }  
  14.       }  
  15.     }  
  16.     return foundValues;  
  17.   }  
[java]  view plain  copy
  1. private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)  
  2.       throws SQLException {  
  3.     final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);  
  4.     boolean foundValues = false;  
  5.     final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();  
  6.     for (ResultMapping propertyMapping : propertyMappings) {  
  7.       String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);  
  8.       if (propertyMapping.getNestedResultMapId() != null) {  
  9.         // the user added a column attribute to a nested result map, ignore it  
  10.         column = null;  
  11.       }  
  12.       if (propertyMapping.isCompositeResult()  
  13.           || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))  
  14.           || propertyMapping.getResultSet() != null) {  
  15.         Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);  
  16.         // issue #541 make property optional  
  17.         final String property = propertyMapping.getProperty();  
  18.         // issue #377, call setter on nulls  
  19.         if (value != DEFERED  
  20.             && property != null  
  21.             && (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()))) {  
  22.           metaObject.setValue(property, value);  
  23.         }  
  24.         if (property != null && (value != null || value == DEFERED)) {  
  25.           foundValues = true;  
  26.         }  
  27.       }  
  28.     }  
  29.     return foundValues;  
  30.   }  
请注意上述第一个方法,它调用了如下方法:

[java]  view plain  copy
  1. private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {  
  2.     final String mapKey = resultMap.getId() + ":" + columnPrefix;  
  3.     List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);  
  4.     if (autoMapping == null) {  
  5.       autoMapping = new ArrayList<UnMappedColumnAutoMapping>();  
  6.       final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);  
  7.       for (String columnName : unmappedColumnNames) {  
  8.         String propertyName = columnName;  
  9.         if (columnPrefix != null && !columnPrefix.isEmpty()) {  
  10.           // When columnPrefix is specified,  
  11.           // ignore columns without the prefix.  
  12.           if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {  
  13.             propertyName = columnName.substring(columnPrefix.length());  
  14.           } else {  
  15.             continue;  
  16.           }  
  17.         }  
  18.         final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());  
  19.         if (property != null && metaObject.hasSetter(property)) {  
  20.           final Class<?> propertyType = metaObject.getSetterType(property);  
  21.           if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {  
  22.             final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);  
  23.             autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));  
  24.           } else {  
  25.             configuration.getAutoMappingUnknownColumnBehavior()  
  26.                     .doAction(mappedStatement, columnName, property, propertyType);  
  27.           }  
  28.         } else{  
  29.           configuration.getAutoMappingUnknownColumnBehavior()  
  30.                   .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);  
  31.         }  
  32.       }  
  33.       autoMappingsCache.put(mapKey, autoMapping);  
  34.     }  
  35.     return autoMapping;  
  36.   }  
在这里可以窥视到从数据表的列如何映射到对象的属性的一点端倪了:

首先把resultMap中取得的列名转换为大写字母,再截取它的前缀(去除特殊字符),把这个前缀和要映射到的对象的属性进行比对,符合的就映射过去,即对POJO对象注入对应属性值。这里应该不受到字母大小写的影响。

当然,鉴于本人的水平是在有限,做的这个解释还非常模糊,读者有兴趣可以自行研究一下源码。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值