深入浅出Mybatis源码解析——结果集映射流程

前言

在前一篇文章中,笔者补上了一篇文章,在那篇文章中,主要说了关于SQL语句的执行流程,当SQL执行完成后,那么肯定会返回一个结果集,对于这样的结果集是需要做一个映射的,就譬如你所返回的数据是表中的数据,但是要映射成Java中的实体类对象,便需要进行映射处理。

说起来很抽象,那么就不多说了,还是直接进入主题,来看看在源码中是怎么实现的。

一、包装结果列元信息对象

在进入正文之前,还需要再回顾一下关于结果集映射的入口代码:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行PreparedStatement,也就是执行SQL语句
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

在上面的代码中可以看待处理结果集的入口的方法是handleResultSets这个方法。那么就直接进去看看方法中的代码是怎么实现的,代码如下:

	@Override
	public List<Object> handleResultSets(Statement stmt) throws SQLException {
		ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

		// <select>标签的resultMap属性,可以指定多个值,多个值之间用逗号(,)分割
		final List<Object> multipleResults = new ArrayList<>();

		int resultSetCount = 0;
		// 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
		ResultSetWrapper rsw = getFirstResultSet(stmt);

		// 这里是获取所有要映射的ResultMap(按照逗号分割出来的)
		List<ResultMap> resultMaps = mappedStatement.getResultMaps();
		// 要映射的ResultMap的数量
		int resultMapCount = resultMaps.size();
		validateResultMapsCount(rsw, resultMapCount);
		// 循环处理每个ResultMap,从第一个开始处理
		while (rsw != null && resultMapCount > resultSetCount) {
			// 得到结果映射信息
			ResultMap resultMap = resultMaps.get(resultSetCount);
			// 处理结果集
			// 从rsw结果集参数中获取查询结果,再根据resultMap映射信息,将查询结果映射到multipleResults中
			handleResultSet(rsw, resultMap, multipleResults, null);

			rsw = getNextResultSet(stmt);
			cleanUpAfterHandlingResultSet();
			resultSetCount++;
		}

		// 对应<select>标签的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);
	}

上面的代码还是挺复杂的,但是本节,首先看看getFirstResultSet(stmt)方法的实现:

	private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
		ResultSet rs = stmt.getResultSet();
		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 resultset
					break;
				}
			}
		}
		return rs != null ? new ResultSetWrapper(rs, configuration) : null;
	}

从上面代码中可以看出逻辑大致如下:

  • 首先是通过statement获取结果集
  • 判断结果集是否为null,如果为null,则获取第一个结果集,如果为true,则进行获取,反之则执行下面
  • 最后如果所获取到的结果集不为null,则新建ResultSetWrapper对象,对ResultSet和configuration进行封装。

最后我们来看看这个ResultSetWrapper对象的构造器:

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    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));
    }
  }

简单分析下这个构造器做了哪些主要操作:

  1. 通过configuration获取类型处理注册表
  2. 通过结果集获取元数据
  3. 通过ResultSetMetaData获取列数
  4. 然后进行遍历

二、循环处理每个ResultMap

从handleResultSet(rsw, resultMap, multipleResults, null)方法进入,来看循环处理每个ResultMap到底是如何进行处理的:

	private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
			ResultMapping parentMapping) throws SQLException {
		try {
			// 处理嵌套结果映射
			if (parentMapping != null) {
				handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
			} else {
				// 第一次一般来说resultHandler为空,则创建DefaultResultHandler来处理结果
				if (resultHandler == null) {
					DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
					// 处理行数据,其实就是完成结果映射
					handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
					
					multipleResults.add(defaultResultHandler.getResultList());
				} else {
					handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
				}
			}
		} finally {
			// issue #228 (close resultsets)
			closeResultSet(rsw.getResultSet());
		}
	}

先简单说下这段代码的逻辑功能:

  1. 处理嵌套结果映射
  2. resultHandler为空,则创建DefaultResultHandler来处理结果
  3. 处理行数据,其实就是完成结果映射
  4. 如果抛异常,便关闭结果集

1)、处理嵌套结果映射

	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);
		}
	}

这里简单的说下简单结果集映射的实现,代码如下:

	private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
			ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
		DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
		// 获取结果集信息
		ResultSet resultSet = rsw.getResultSet();
		// 使用rowBounds的分页信息,进行逻辑分页(也就是在内存中分页)
		skipRows(resultSet, rowBounds);
		while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
			// 通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
			ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
			// 将查询结果封装到POJO中
			Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
			// 处理对象嵌套的映射关系
			storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
		}
	}

这段代码中简单的说下代码中的逻辑:

  1. 通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
  2. 将查询结果封装到POJO中
  3. 处理对象嵌套的映射关系

(1)、通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别

	public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
			throws SQLException {
		Set<String> pastDiscriminators = new HashSet<>();
		// 获取<resultMap>标签的子标签<discriminator>
		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;
	}

(2)、将查询结果封装到POJO中

	private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
		// 延迟加载的映射信息
		final ResultLoaderMap lazyLoader = new ResultLoaderMap();
		// 创建要映射的PO类对象
		Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
		if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
			final MetaObject metaObject = configuration.newMetaObject(rowValue);
			boolean foundValues = this.useConstructorMappings;
			// 是否应用自动映射,也就是通过resultType进行映射
			if (shouldApplyAutomaticMappings(resultMap, false)) {
				// 根据columnName和type属性名映射赋值
				foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
			}
			// 根据我们配置ResultMap的column和property映射赋值
			// 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值
			foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
			foundValues = lazyLoader.size() > 0 || foundValues;
			rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
		}
		return rowValue;
	}

(3)、处理对象嵌套的映射关系

	private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
			Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
		if (parentMapping != null) {
			linkToParents(rs, parentMapping, rowValue);
		} else {
			callResultHandler(resultHandler, resultContext, rowValue);
		}
	}

2)、如果只有一个结果集合,则直接从多结果集中取出第一个

其实这个实现很简单,只是从一个集合里去除第一条数据而已:

	private List<Object> collapseSingleResultList(List<Object> multipleResults) {
		return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
	}

 

总结

其实最后这这个结果集的映射实现是挺复杂的,不过由于篇幅原因,只能做一个简单的分析,当然这也是本系列的最后一篇文章了。但是我们还是来一个最后的总结吧。最后还是感谢大家的关注,后面笔者将会写其他系列的文章了,不过可能又会有大段时间的断更,因为笔者想好好闭关一下。

 

 

  • DefaultResultSetHandler#handleResultSets
    • DefaultResultSetHandler#handleResultSet
      • DefaultResultSetHandler#handleRowValues
        • DefaultResultSetHandler#handleRowValuesForSimpleResultMap
          • DefaultResultSetHandler#getRowValue
            • DefaultResultSetHandler#createResultObject:创建映射结果对象
            • DefaultResultSetHandler#applyAutomaticMappings
            • DefaultResultSetHandler#applyPropertyMappings

 

发布了42 篇原创文章 · 获赞 9 · 访问量 6085
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 1024 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览