Mybatis源码阅读(三):结果集映射3.1 —— ResultSetBuilder与简单映射

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************
前言

在前面的文章中,已经介绍了三种SqlSource的代码,下面开始介绍执行select语句对查询结果集封装的过程。
ResultSetHandler

前面的文章中得知,mybatis会将结果集按照映射配置文件中定义的映射规则,如resultMap节点,映射成相应的结果对象。

在StatementHandler接口执行完指定的select语句后,会将查询结果集交给ResultSetHandler完成映射处理。

ResultSetHandler接口代码如下:

/**

  • 处理select查询的结果集

  • @author Clinton Begin
    */
    public interface ResultSetHandler {

    /**

    • 处理结果集,生成结果集集合
    • @param stmt
    • @param
    • @return
    • @throws SQLException
      */
      List handleResultSets(Statement stmt) throws SQLException;

    /**

    • 处理结果集,返回相应的游标
    • @param stmt
    • @param
    • @return
    • @throws SQLException
      */
      Cursor handleCursorResultSets(Statement stmt) throws SQLException;

    /**

    • 处理存储过程
    • @param cs
    • @throws SQLException
      */
      void handleOutputParameters(CallableStatement cs) throws SQLException;

}

ResultSetHandler只有DefaultResultSetHandler一个实现,该类是处理结果集映射的核心类。核心字段如下所示:

public class DefaultResultSetHandler implements ResultSetHandler {

private static final Object DEFERRED = new Object();/*** MyBatis执行器*/
private final Executor executor;
private final Configuration configuration;
/*** Sql节点*/
private final MappedStatement mappedStatement;
/*** 游标*/
private final RowBounds rowBounds;
/*** 参数处理器*/
private final ParameterHandler parameterHandler;
/*** 结果集处理器*/
private final ResultHandler<?> resultHandler;
/*** Sql对象*/
private final BoundSql boundSql;
private final TypeHandlerRegistry typeHandlerRegistry;
/*** 对象工厂和反射工厂*/
private final ObjectFactory objectFactory;
private final ReflectorFactory reflectorFactory;/*** 映射缓存*/
private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
private final Map<String, Object> ancestorObjects = new HashMap<>();/*** 自动映射列缓存*/
private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();/*** 记录是否使用构造器创建映射对象*/
private boolean useConstructorMappings;

}

handlerResultSets方法

通过select语句查询司机卡得到的结果集由handlerResultSets方法进行处理。该方法可以处理由Statement、PreparedStatement、CallableStatement产生的结果集。其中,Statement用于处理静态SQL,PrepareStatement用于处理预处理的SQL,CallableStatement用于处理存储过程,存储过程的结果集可能有多个,mybatis中对多结果集也进行了处理。由于java开发多数是mysql,而mysql中存储过程使用频率非常之少,因此这里不对多结果集进行讲解。

handleResultSets方法的代码如下。

/**
* ☆
* select查询到的结果集会在这里被处理
*
* @param stmt
* @return
* @throws SQLException
*/
@Override
public List handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity(“handling results”).object(mappedStatement.getId());

    // 保存映射得到的结果集对象final List<Object> multipleResults = new ArrayList<>();// 结果集数量int resultSetCount = 0;// 获取第一个结果集ResultSetWrapper rsw = getFirstResultSet(stmt);// 获取到sql节点所有的resultMap(一般只有一个)List<ResultMap> resultMaps = mappedStatement.getResultMaps();// resultMap的数量int resultMapCount = resultMaps.size();validateResultMapsCount(rsw, resultMapCount);// 遍历resultMapswhile (rsw != null && resultMapCount > resultSetCount) {// 获取指定下标的resultMapResultMap resultMap = resultMaps.get(resultSetCount);// 处理resultSethandleResultSet(rsw, resultMap, multipleResults, null);// 获取下一个结果集rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}// resultSets是多结果集时适用。实际开发中几乎不用这个String[] resultSets = mappedStatement.getResultSets();if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();ResultMap resultMap = configuration.getResultMap(nestedResultMapId);handleResultSet(rsw, resultMap, null, parentMapping);}rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}}return collapseSingleResultList(multipleResults);
}

查询到的结果集可能有多个,mybatis默认先处理单结果集,getFirstResultSet方法用于获取第一个结果集对象。而getNextResultSet则是用于获取下一个结果集

/*** 获取第一个结果集对象。* 在操作存储过程时,可能会得到多个结果集* 该方法只获取第一个结果集** @param stmt* @return* @throws SQLException*/
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {// 获取结果集ResultSet rs = stmt.getResultSet();// 结果集如果为null就继续向下获取while (rs == null) {// move forward to get the first resultset in case the driver// doesn't return the resultset as the first result (HSQLDB 2.1)if (stmt.getMoreResults()) {rs = stmt.getResultSet();} else {if (stmt.getUpdateCount() == -1) {// no more results. Must be no resultsetbreak;}}}return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}/*** 获取下一个resultSet结果集** @param stmt* @return*/
private ResultSetWrapper getNextResultSet(Statement stmt) {// Making this method tolerant of bad JDBC driverstry {// 检测jdbc是否支持多结果集if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {// 检测是否还存在需要处理的结果集if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {ResultSet rs = stmt.getResultSet();if (rs == null) {return getNextResultSet(stmt);} else {return new ResultSetWrapper(rs, configuration);}}}} catch (Exception e) {// Intentionally ignored.}return null;
}

在上面的代码中,DefaultResultHandler在获取到结果集对象之后,会将其封装成ResultSetWrapper对象再进行处理。ResultSetWrapper对象中记录了结果集的一些元数据,并提供了一系列操作ResultSet的辅助方法,下面是ResultSetWrapper的核心字段。

/**

  • 对ResultSet进行封装

  • 存放了ResultSet的元数据

  • @author Iwao AVE!
    */
    public class ResultSetWrapper {

    /**

    • 查询得到的结果集
      /
      private final ResultSet resultSet;
      /
      *
    • 一堆类型处理器
      /
      private final TypeHandlerRegistry typeHandlerRegistry;
      /
      *
    • resultSet每列列名
      /
      private final List columnNames = new ArrayList<>();
      /
      *
    • 每列对应的java类型
      /
      private final List classNames = new ArrayList<>();
      /
      *
    • 每列对应的jdbc类型
      /
      private final List jdbcTypes = new ArrayList<>();
      /
      *
    • key是列名,value是TypeHandler
      /
      private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
      /
      *
    • 记录被映射的列名
      /
      private final Map<String, List> mappedColumnNamesMap = new HashMap<>();
      /
      *
    • 记录未映射的列名
      */
      private final Map<String, List> unMappedColumnNamesMap = new HashMap<>();
      }

在ResultSetWrapper的构造方法中,会初始化columnNames、jdbcTypes、classNames三个集合,代码如下。

public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {super();this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.resultSet = rs;// 获取resultSet的元信息final ResultSetMetaData metaData = rs.getMetaData();// 获取resultSet列数final int columnCount = metaData.getColumnCount();// 遍历每一列,封装 列名、jdbc类型、java类型for (int i = 1; i <= columnCount; i++) {columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));classNames.add(metaData.getColumnClassName(i));}
}

简单映射

介绍完整体的流程,下面来看handleResultSet方法。该方法的核心功能是完成对单个结果集的映射(即单表查询的映射)。代码如下。

/*** 根据resultMap定义的映射规则去处理resultSet。并将映射的结果添加到multipleResults集合** @param rsw* @param resultMap* @param multipleResults* @param parentMapping* @throws SQLException*/
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {// 处理结果集中的嵌套映射。(resultMap中套着resultMap)handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else {if (resultHandler == null) {// 用户没有指定resultHandler,就用DefaultResultHandlerDefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);// 对resultSet进行映射,并将映射结果添加到defaultResultHandlerhandleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);// 将defaultResultHandler中保存的集合添加到multipleResultsmultipleResults.add(defaultResultHandler.getResultList());} else {// 使用用户指定的resultHandler处理结果集handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}
}

该方法的核心代码就是handleRowValues。方法中判断是否包含嵌套映射去决定处理简单映射还是嵌套映射,代码如下。

/*** 结果集映射核心方法** @param rsw* @param resultMap* @param resultHandler* @param rowBounds* @param parentMapping* @throws SQLException*/
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);}
}

下面先看简单映射。简单映射的核心代码在handleRowValuesForSimpleResultMap方法中,下面先分析该方法执行流程。

调用skipRows方法,根据RowBounds中的offset值定位到指定的记录行。RowBounds叫做游标,后面的文章会对其进行介绍。
调用shouldProcessMoreRows方法检测是否还有需要映射的记录。
通过resolveDiscriminatedResultMap方法确定映射要使用的ResultMap对象
调用getRowValue方法对Result中的一行记录进行映射。通过createResultObject方法创建映射后的结果对象。通过shouldApplyAutomaticMap平时方法检测是否开启了自动映射功能通过applyAutomaiticMappings方法自动映射ResultMap中为明确映射的列通过applyPropertyMap平时方法映射ResultMap中明确映射的列,到这里该行记录的数据已经完全映射到了结果对象的相应属性中。
调用storeObject方法保存映射得到的结果集对象

handleRowValuesForSimpleResultMap代码如下。

/*** 简单结果集映射处理** @param rsw* @param resultMap* @param resultHandler* @param rowBounds* @param parentMapping* @throws SQLException*/
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();ResultSet resultSet = rsw.getResultSet();// 通过游标的offset值定位到指定的记录行skipRows(resultSet, rowBounds);// 检测是否还有需要映射的记录while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {// 确定使用的ResultMap对象。多数情况下,这里指的还是传入的ResultMapResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);// 映射Object rowValue = getRowValue(rsw, discriminatedResultMap, null);// 保存映射结果storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}
}

skipRows方法是根据RowBounds.offset字段的值定位到指定的记录。

/*** 通过游标定位到指定行** @param rs* @param rowBounds* @throws SQLException*/
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {// 直接定位到指定行rs.absolute(rowBounds.getOffset());}} else {for (int i = 0; i < rowBounds.getOffset(); i++) {if (!rs.next()) {break;}}}
}

定位到指定的记录行之后,再通过shouldProcessMoreRows方法检测是否还有需要映射的行。

/*** 检测是否还有需要映射的数据** @param context* @param rowBounds* @return*/
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}

resolveDiscriminatedResultMap方法会根据ResultMap对象中记录的Discriminator以及参与映射的列值,选择映射操作最终使用的ResultMap,具体实现如下,。

/*** 根据ResultMap中记录的Discriminator对象以及参与映射的记录行中的列值* 确定使用的ResultMap对象** @param rs* @param resultMap* @param columnPrefix* @return* @throws SQLException*/
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {Set<String> pastDiscriminators = new HashSet<>();// 通过discriminator标签去确定使用哪个ResultMap。使用不多,不进行注释Discriminator discriminator = resultMap.getDiscriminator();while (discriminator != null) {final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));if (configuration.hasResultMap(discriminatedMapId)) {resultMap = configuration.getResultMap(discriminatedMapId);Discriminator lastDiscriminator = discriminator;discriminator = resultMap.getDiscriminator();if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {break;}} else {break;}}return resultMap;
}
private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {final ResultMapping resultMapping = discriminator.getResultMapping();final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}

通过上面方法的处理确定了映射使用的ResultMap对象,之后会调用getRowValue完成对该记录的映射。首先根据ResultMap指定的类型创建对应的结果对象和MetaObject,再根据配置信息决定是否自动映射ResultMap中未明确映射的列,映射完毕后返回结果对象。代码如下。

/*** 映射** @param rsw* @param resultMap* @param columnPrefix* @return* @throws SQLException*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {// 与延迟加载有关final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 创建该行记录映射之后的结果对象,就是resultMap的type属性指定的类Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {// 创建上面对象的metaObjectfinal MetaObject metaObject = configuration.newMetaObject(rowValue);// 成功映射任意属性,则为true,否则为falseboolean foundValues = this.useConstructorMappings;// 检测是否需要自动映射if (shouldApplyAutomaticMappings(resultMap, false)) {// 自动映射resultMap未指定的列foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 映射resultMap指定的列foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;// 如果没有映射任何属性,就根据mybatis-config.xml配置的returnInstanceForEmptyRow配置决定如何返回rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}

该方法中,createResultObject方法负责创建数据库记录映射得到的结果对象,该方法会返回结果集的列数、constructorResultMappings集合等信息,选择不同的方式创建结果对象。具体实现如下。

/*** 创建该行记录映射之后的结果对象,就是resultMap的type属性指定的类** @param rsw* @param resultMap* @param lazyLoader* @param columnPrefix* @return* @throws SQLException*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {this.useConstructorMappings = false; // reset previous mapping result// 构造的参数类型final List<Class<?>> constructorArgTypes = new ArrayList<>();// 构造参数final List<Object> constructorArgs = new ArrayList<>();// 创建该行记录的结果对象。该方法是该步骤的核心Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);// TODO 如果包含嵌套查询且配置了延迟加载,就创建代理对象if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// issue gcode #109 && issue #149if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);break;}}}// 记录是否使用构造器创建对象this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping resultreturn resultObject;
}/*** 创建映射结果对象** @param rsw* @param resultMap* @param constructorArgTypes* @param constructorArgs* @param columnPrefix* @return* @throws SQLException*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)throws SQLException {// 根据resultMap配置的type属性去创建对应的MetaClassfinal Class<?> resultType = resultMap.getType();final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);// 获取到constructor节点final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();if (hasTypeHandlerForResultObject(rsw, resultType)) {// 结果集只有一列,并且存在TypeHandler对象可以将该列转换成resultType指定的值(Integer、String等)return createPrimitiveResultObject(rsw, resultMap, columnPrefix);} else if (!constructorMappings.isEmpty()) {// resultMap中指定了constructor标签,通过反射方式调用构造方法创建对象return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {// 使用默认的无参构造创建return objectFactory.create(resultType);} else if (shouldApplyAutomaticMappings(resultMap, false)) {// 通过自动映射,查找合适的构造方法创建return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);}throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

完成了对映射结果对象的创建后,下面就会将一行记录的各个列映射到该结果集对象的对应属性中。在成功创建对象并且获取到MetaObject之后,会调用shouldApplyAutomaticMappings方法检测是否允许自动映射,如果允许则调用applyAutomaiticMappings方法对ResultMap未指定的列进行自动映射。

/*** 是否需要自动映射。** @param resultMap* @param isNested* @return*/
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {if (resultMap.getAutoMapping() != null) {return resultMap.getAutoMapping();} else {if (isNested) {return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();} else {return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();}}
}/*** 自动映射未指定的列* @param rsw* @param resultMap* @param metaObject* @param columnPrefix* @return* @throws SQLException*/
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {// 查找需要自动映射的列List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {// 映射列不为空,一一映射for (UnMappedColumnAutoMapping mapping : autoMapping) {// 从resultSet获取值final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')metaObject.setValue(mapping.property, value);}}}return foundValues;
}

createAutomaticMappings方法负责为未映射的列查找对应的属性,并将二者关联起来封装成UnMappedColumnAutoMapping对象。createAutomaticMappings方法的具体实现如下。

/*** 查找需要自动映射的列* @param rsw* @param resultMap* @param metaObject* @param columnPrefix* @return* @throws SQLException*/
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {final String mapKey = resultMap.getId() + ":" + columnPrefix;// 先从缓存中找List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);if (autoMapping == null) {autoMapping = new ArrayList<>();// 获取未映射的列final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);for (String columnName : unmappedColumnNames) {// 默认列名就是属性名String propertyName = columnName;// 列前缀不为空时处理。if (columnPrefix != null && !columnPrefix.isEmpty()) {// When columnPrefix is specified,// ignore columns without the prefix.if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {propertyName = columnName.substring(columnPrefix.length());} else {continue;}}// 根据列名查找对应的属性final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());// 属性不为空并且有set方法if (property != null && metaObject.hasSetter(property)) {// 该列已经映射,不重复映射if (resultMap.getMappedProperties().contains(property)) {continue;}final Class<?> propertyType = metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {// 查找对应的TypeHandler对象final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);// 将该列添加到autoMapping集合中autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));} else {configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType);}} else {configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);}}// 存放到缓存autoMappingsCache.put(mapKey, autoMapping);}return autoMapping;
}

通过applyAutomaiticMappings方法处理完自动映射之后,后续会通过applyPropertyMappings方法对ResultMap中指定的列进行映射,核心代码如下。

/*** 根据配置去映射* @param rsw* @param resultMap* @param metaObject* @param lazyLoader* @param columnPrefix* @return* @throws SQLException*/
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;// 获取所有resultMappingfinal List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// 获取列名String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);if (propertyMapping.getNestedResultMapId() != null) {// 判断该节点是否是对其他ResultMapping进行引用column = null;}// 场景1:column是{prop1=col1,prop2=col2}形式if (propertyMapping.isCompositeResult()// 场景2:基本类型的属性映射|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))// 场景3:多结果集处理|| propertyMapping.getResultSet() != null) {// 完成映射,得到属性值Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);// 获取属性名称final String property = propertyMapping.getProperty();if (property == null) {continue;} else if (value == DEFERRED) {// DEFERRED指占位符对象foundValues = true;continue;}if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')// 设置属性值metaObject.setValue(property, value);}}}return foundValues;
}

其中,映射操作是在getPropertyMappingValue方法中完成,具体代码如下,

/*** 完成映射操作并获取属性值* @param rs* @param metaResultObject* @param propertyMapping* @param lazyLoader* @param columnPrefix* @return* @throws SQLException*/
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);return DEFERRED;} else {final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);// 使用typeHandler来获取属性值return typeHandler.getResult(rs, column);}
}

到这里,已经得到了一个完整映射的结果对象,之后DefaultResultSetHandler会通过storeObject方法将该结果对象保存到合适的位置,这样该行记录就完成了。如果是嵌套映射或者嵌套查询的结果对象则保存到父对象对应的属性中,如果是简单映射则保存到ResultHandler中。

/*** 保存映射结果* @param resultHandler* @param resultContext* @param rowValue* @param parentMapping* @param rs* @throws SQLException*/
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {if (parentMapping != null) {// 嵌套映射,保存在父对象属性中linkToParents(rs, parentMapping, rowValue);} else {// 普通映射,保存在ResultHandlercallResultHandler(resultHandler, resultContext, rowValue);}
}private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {resultContext.nextResultObject(rowValue);((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

至此,简单映射的流程就介绍完了。
*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536975.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kdj买卖指标公式源码_通达信指标公式源码MACD背离KDJ背离指标

N1:5;N2:10;N3:21;N4:60;牛熊:EMA(CLOSE,N4),COLORGREEN,LINETHICK3;DIFF:EMA(CLOSE,12) - EMA(CLOSE,26);DEA:EMA(DIFF,8);A1:BARSLAST(REF(CROSS(DIFF,DEA),1)); B1:REF(C,A11)>C AND REF(DIFF,A11)DRAWTEXT(IF(B1>0,1,0),L-0.1,MACD底背),COLORGREEN;RSV:(CLOSE-LLV(L…

Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

gridview获取选中行数据_Word转Excel,不想熬夜加班,那就掌握这个数据清洗方法...

私信回复关键词【福利】~获取丰富办公资源&#xff0c;助你高效办公早下班&#xff01;小伙伴们&#xff0c;大家好&#xff0c;我是专治各种疑难杂「数」的农夫~今天&#xff0c;我就为大家介绍一种高效的数据清洗方法&#xff0c;助你告别熬夜加班&#xff0c;拥抱美好的夜晚…

Mybatis源码阅读(三):结果集映射3.3 —— 主键生成策略

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

list最大容量_Java 基础(四)集合源码解析 List

List 接口前面我们学习了Iterator、Collection&#xff0c;为集合的学习打下了基础&#xff0c;现在我们来学习集合的第一大体系 List。List 是一个接口&#xff0c;定义了一组元素是有序的、可重复的集合。List 继承自 Collection&#xff0c;较之 Collection&#xff0c;List…

Mybatis源码阅读(四):核心接口4.1——StatementHandler

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Shell学习之结合正则表达式与通配符的使用(五)

Shell学习之结合正则表达式与通配符的使用 目录 通配符 正则表达式与通配符通配符通配符的使用正则表达式 正则表达式正则表达式的使用通配符 正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串&#xff0c;正则是包含匹配。grep、awk、sed等命令可以支持正则表达…

Mybatis源码阅读(四):核心接口4.2——Executor(上)

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

接收xml参数_SpringBoot实战(二):接收xml请求

强烈推荐一个大神的人工智能的教程&#xff1a;http://www.captainbed.net/zhanghan【前言】最近在对接一个第三方系统&#xff0c;需要接收第三方系统的回调&#xff0c;而且格式为XML形式&#xff0c;之前自己一般接收的参数是Json形式&#xff0c;于是乎做个实验验证一下使用…

报错 插入更新_window如何解决mysql数据量过大导致的报错

window如何解决报错“The total number of locks exceeds the lock table size”第一大步&#xff0c;查看mysql配置信息在CMD中输入mysql -hlocalhost -uroot -p #如果设置了密码直接接在p 后面 show variables like %storage_engine%以下为结果可以看到InnoDB是MySQL的默认引…

Mybatis源码阅读(四):核心接口4.2——Executor(下)

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Mybatis源码阅读(五 ):接口层——SqlSession

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

插入公式_一个小工具,彻底帮你搞定在Markdown中插入公式的问题

在编辑Markdown文档时&#xff0c;插入公式是一个挺麻烦的活儿。需要掌握LaTex语法。我自己看完语法后&#xff0c;直接放弃&#xff0c;这绝对是反人类的语法。&#xff08;好吧&#xff0c;是我不会用...&#xff09;但是&#xff0c;我相信你看了这篇文章后&#xff0c;绝对…

Mybatis源码阅读(一):Mybatis初始化1.2 —— 解析别名、插件、对象工厂、反射工具箱、环境

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Google 修改 Chrome API,防止隐身模式检测

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; 在使用 Chrome 浏览网页时&#xff0c;某些网站会使用某种方法来确定访问者是否处于隐身模式&#xff0c;这是一种隐私泄漏行为。Google 目前正在考虑修改 Chrome 的相关 API&#xff0c;来杜绝…

Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

JavaScript异步基础

唯一比不知道代码为什么崩溃更可怕的事情是&#xff0c;不知道为什么一开始它是工作的&#xff01;在 ECMA 规范的最近几次版本里不断有新成员加入&#xff0c;尤其在处理异步的问题上&#xff0c;更是不断推陈出新。然而&#xff0c;我们在享受便利的同时&#xff0c;也应该了…

Flutter、ReactNative、uniapp对比

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

多线程中ThreadLocal的使用

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

注解版poi操作工具

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…