Mybatis原理:结果集封装详解

x33g5p2x  于2022-05-06 转载在 其他  
字(15.8k)|赞(0)|评价(0)|浏览(578)

​ 经过sql参数解析、sql动态组装和执行sql,相对而言,结果集的封装,是mybatis数据处理的最后一环。这里只对查询结果而言,因为更新语句一般都是返回影响的行数。抛开mybatis,如果让我们组装结果,我们该如何进行呢?mybatis的查询结果统一表示为:

  1. List<E>

即使是查询单个对象,它的查询结果还是封装成 List 对象,然后返回list集合的第一个元素。

个人根据mybatis的源码,将mybatis对结果集的封装,分成两步:

(1)通过反射,创建结果对象,其所有属性为默认值,例如,如果结果是实体对象,那么将通过无参构造函数创建对象,其所有属性一般为空,如果结果是List,则会创建一个空的List

(2)为结果对象的属性赋值,这里也是通过反射,找到set方法赋值

下面开始进入主题:

一、数据准备

  1. 查询sql以及自定义映射的resultMap:
  1. <resultMap id="userMap" type="User">
  2. <id property="id" column="id"></id>
  3. <result property="username" column="username"></result>
  4. <result property="password" column="password"></result>
  5. <result property="isValid" column="is_valid"></result>
  6. <collection property="blogs" javaType="java.util.List" ofType="Blog">
  7. <id property="id" column="blog_id"></id>
  8. <result property="title" column="title"></result>
  9. <result property="userId" column="user_id"></result>
  10. </collection>
  11. </resultMap>
  12. <select id="getUserAndBlogByUserId" parameterType="string" resultMap="userMap">
  13. select u.id,u.username,u.password,u.is_valid,b.id as blog_id,b.title,b.user_id
  14. from t_user u LEFT JOIN t_blog b ON u.id = b.user_id
  15. where u.id=#{id}
  16. </select>

从查询sql,你也可以发现一二,比如博客表t_blog中,含有一个逻辑外键user_id,表示该博客属于哪个用户的,而每个用户可以拥有多个博客,显然是一对多的关系,而查询条件则为用户id。

2.实体类

  1. public class User implements Serializable{
  2. private String id;
  3. private String username;
  4. private String password;
  5. private Integer isValid;
  6. //一个用户,对应多篇博客
  7. private List<Blog> blogs;
  8. }
  1. public class Blog implements Serializable{
  2. private String id;
  3. private String title;
  4. private String userId;
  5. }

3.mapper接口

  1. public interface UserMapper {
  2. //根据id查询用户及其所有博客
  3. User getUserAndBlogByUserId(String id);
  4. }

4.测试方法

  1. public class One2ManyQuery {
  2. public static void main(String[] args) throws IOException {
  3. //读取配置信息
  4. InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
  5. //根据配置信息,创建SqlSession工厂
  6. SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
  7. //SqlSession工厂创建SqlSession
  8. SqlSession sqlSession = factory.openSession();
  9. //获取接口的代理对象
  10. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  11. User user = mapper.getUserAndBlogByUserId("123");
  12. System.out.println(user);
  13. }
  14. }

二、结果对象的创建

​ 本次程序的起点是PreparedStatementHandler的方法,它是查询的结束,同时也是结果封装的开始入口:

  1. @Override
  2. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3. PreparedStatement ps = (PreparedStatement) statement;
  4. ps.execute();
  5. return resultSetHandler.handleResultSets(ps);
  6. }

​ 由此,进入到 resultSetHandler.handleResultSets(ps) 方法,而默认会进入到的DefaultResultSetHandler的handleResultSets方法:

  1. @Override
  2. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  3. ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  4. // 要返回的结果
  5. final List<Object> multipleResults = new ArrayList<>();
  6. // 迭代变量,结果集的个数
  7. int resultSetCount = 0;
  8. // 获取第一个结果集,并包装成ResultSetWrapper对象,
  9. // ResultSetWrapper对象含有已映射和未映射的列名和属性的对应关系
  10. ResultSetWrapper rsw = getFirstResultSet(stmt);
  11. // 获取所有的ResultMap
  12. List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  13. // ResultMap的个数
  14. int resultMapCount = resultMaps.size();
  15. // 校验:如果结果集有数据,但是没有定义返回的结果类型,就会报错
  16. validateResultMapsCount(rsw, resultMapCount);
  17. while (rsw != null && resultMapCount > resultSetCount) {
  18. // 依次获取ResultMap
  19. ResultMap resultMap = resultMaps.get(resultSetCount);
  20. // 处理结果集,这里是重点
  21. handleResultSet(rsw, resultMap, multipleResults, null);
  22. // 获取下一个结果集
  23. rsw = getNextResultSet(stmt);
  24. cleanUpAfterHandlingResultSet();
  25. resultSetCount++;
  26. }
  27. String[] resultSets = mappedStatement.getResultSets();
  28. if (resultSets != null) {
  29. while (rsw != null && resultSetCount < resultSets.length) {
  30. ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
  31. if (parentMapping != null) {
  32. String nestedResultMapId = parentMapping.getNestedResultMapId();
  33. ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  34. handleResultSet(rsw, resultMap, null, parentMapping);
  35. }
  36. rsw = getNextResultSet(stmt);
  37. cleanUpAfterHandlingResultSet();
  38. resultSetCount++;
  39. }
  40. }
  41. return collapseSingleResultList(multipleResults);
  42. }

那么,我们重点关注一下这句,因为它才是真正的处理结果集:

  1. // 处理结果集,这里是重点
  2. handleResultSet(rsw, resultMap, multipleResults, null);
  1. private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  2. try {
  3. if (parentMapping != null) {
  4. handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
  5. } else {
  6. if (resultHandler == null) {
  7. // 如果结果处理器为空,则使用默认的结果处理器,没有自定义的情况下,都是走这个流程
  8. DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
  9. // 处理每一行的值
  10. handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  11. // 将处理结果放到list集中
  12. multipleResults.add(defaultResultHandler.getResultList());
  13. } else {
  14. handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
  15. }
  16. }
  17. } finally {
  18. // issue #228 (close resultsets)
  19. closeResultSet(rsw.getResultSet());
  20. }
  21. }

根据上面的代码,我们关注这一句代码是如何处理的:

  1. // 处理每一行的值
  2. handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);

查看详细的 handleRowValues 方法:

  1. public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  2. // 如果有嵌套的ResultMap
  3. if (resultMap.hasNestedResultMaps()) {
  4. ensureNoRowBounds();
  5. checkResultHandler();
  6. // 处理含有嵌套ResultMap的结果
  7. handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  8. } else {
  9. // 处理不含有嵌套ResultMap的结果
  10. handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  11. }
  12. }

由于我们使用了 collection 标签做一对多的映射,所以是属于嵌套的resultMap查询,个人理解,即使是一个实体对象,它也是一个resultMap,只不过它的resultType是实体对象罢了,所以走的嵌套分支:

  1. // 处理含有嵌套ResultMap的结果
  2. handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);

这个方法比较长:

  1. private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  2. final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  3. ResultSet resultSet = rsw.getResultSet();
  4. // 跳过已处理的行
  5. skipRows(resultSet, rowBounds);
  6. Object rowValue = previousRowValue;
  7. while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
  8. final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
  9. final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
  10. Object partialObject = nestedResultObjects.get(rowKey);
  11. // issue #577 && #542
  12. if (mappedStatement.isResultOrdered()) {
  13. if (partialObject == null && rowValue != null) {
  14. nestedResultObjects.clear();
  15. storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  16. }
  17. rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
  18. } else {
  19. // 看这行代码,获取行值
  20. rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
  21. if (partialObject == null) {
  22. storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  23. }
  24. }
  25. }
  26. if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
  27. storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  28. previousRowValue = null;
  29. } else if (rowValue != null) {
  30. previousRowValue = rowValue;
  31. }
  32. }

我们找重点看吧:

  1. // 看这行代码,获取行值
  2. rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);

这其中 getRowValue 方法,我认为是比较重要的方法,处理逻辑大部分都在这里:

  1. private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
  2. // resultMap的唯一标志id
  3. final String resultMapId = resultMap.getId();
  4. // 返回值
  5. Object rowValue = partialObject;
  6. if (rowValue != null) {
  7. final MetaObject metaObject = configuration.newMetaObject(rowValue);
  8. putAncestor(rowValue, resultMapId);
  9. applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
  10. ancestorObjects.remove(resultMapId);
  11. } else {
  12. final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  13. // 创建结果对象
  14. rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  15. // 行值不为空,并且结果对象有类型处理器
  16. if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  17. // 将行值包装成元数据对象MetaObject
  18. final MetaObject metaObject = configuration.newMetaObject(rowValue);
  19. boolean foundValues = this.useConstructorMappings;
  20. // 是否使用自动映射,因为我们自定义了resultMap,所以不会走这个
  21. if (shouldApplyAutomaticMappings(resultMap, true)) {
  22. foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
  23. }
  24. // 给对象的属性赋值
  25. foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
  26. putAncestor(rowValue, resultMapId);
  27. // 给嵌套的结果属性赋值
  28. foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
  29. ancestorObjects.remove(resultMapId);
  30. foundValues = lazyLoader.size() > 0 || foundValues;
  31. rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  32. }
  33. if (combinedKey != CacheKey.NULL_CACHE_KEY) {
  34. nestedResultObjects.put(combinedKey, rowValue);
  35. }
  36. }
  37. return rowValue;
  38. }

结果对象的创建,就在这里:

  1. // 创建结果对象
  2. rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

详细如下:

  1. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  2. this.useConstructorMappings = false; // reset previous mapping result
  3. final List<Class<?>> constructorArgTypes = new ArrayList<>();
  4. final List<Object> constructorArgs = new ArrayList<>();
  5. Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  6. if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  7. final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  8. for (ResultMapping propertyMapping : propertyMappings) {
  9. // issue gcode #109 && issue #149
  10. if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  11. resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  12. break;
  13. }
  14. }
  15. }
  16. this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  17. return resultObject;
  18. }

你会发现这句,最终也是通过反射创建对象的:

  1. resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);

三、属性赋值

​ 回到上一步的 DefaultResultSetHandler的getRowValue 方法

​ 通过代码发现,这里的逻辑关系,大概是这样的,sql中的列名 ->通过映射关系,拿到对应的属性名 ->通过属性名,获取set的方法名,最后,通过反射设置值

1.普通属性赋值

  1. // 给对象的属性赋值
  2. foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
  1. private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
  2. throws SQLException {
  3. // 已经映射的列名集合
  4. final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  5. boolean foundValues = false;
  6. // 所有属性和列的映射规则
  7. final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  8. for (ResultMapping propertyMapping : propertyMappings) {
  9. // 列名
  10. String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
  11. if (propertyMapping.getNestedResultMapId() != null) {
  12. // the user added a column attribute to a nested result map, ignore it
  13. column = null;
  14. }
  15. if (propertyMapping.isCompositeResult()
  16. || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
  17. || propertyMapping.getResultSet() != null) {
  18. // 根据列名,获取属性值
  19. Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
  20. // issue #541 make property optional
  21. // 属性名
  22. final String property = propertyMapping.getProperty();
  23. if (property == null) {
  24. continue;
  25. } else if (value == DEFERRED) {
  26. foundValues = true;
  27. continue;
  28. }
  29. if (value != null) {
  30. foundValues = true;
  31. }
  32. if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
  33. // gcode issue #377, call setter on nulls (value is not 'found')
  34. // 给对象的属性设置值
  35. metaObject.setValue(property, value);
  36. }
  37. }
  38. }
  39. return foundValues;
  40. }

这里也就是分3步:

(1)根据列名获取值,在resultSet中,根据列名获取值,这个是很容易拿到的

(2)获取属性名,在我们定义的映射规则中就包含了属性名,这个也可以拿到

(3)给对象的属性设置值,这里还是通过反射技术,调用相应的set方法来设值

2.集合对象的属性的设置

回到 DefaultResultSetHandler的getRowValue 方法

  1. // 给嵌套的结果属性赋值
  2. foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
  1. private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
  2. boolean foundValues = false;
  3. for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
  4. final String nestedResultMapId = resultMapping.getNestedResultMapId();
  5. if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
  6. try {
  7. final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
  8. final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
  9. if (resultMapping.getColumnPrefix() == null) {
  10. // try to fill circular reference only when columnPrefix
  11. // is not specified for the nested result map (issue #215)
  12. Object ancestorObject = ancestorObjects.get(nestedResultMapId);
  13. if (ancestorObject != null) {
  14. if (newObject) {
  15. linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
  16. }
  17. continue;
  18. }
  19. }
  20. final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
  21. final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
  22. Object rowValue = nestedResultObjects.get(combinedKey);
  23. boolean knownValue = rowValue != null;
  24. instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
  25. if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
  26. rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
  27. if (rowValue != null && !knownValue) {
  28. linkObjects(metaObject, resultMapping, rowValue);
  29. foundValues = true;
  30. }
  31. }
  32. } catch (SQLException e) {
  33. throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e);
  34. }
  35. }
  36. }
  37. return foundValues;
  38. }

我们着重看这句:

  1. // 实例化集合属性
  2. instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);

这一步,便会处理集合属性:

  1. private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
  2. final String propertyName = resultMapping.getProperty();
  3. Object propertyValue = metaObject.getValue(propertyName);
  4. if (propertyValue == null) {
  5. Class<?> type = resultMapping.getJavaType();
  6. if (type == null) {
  7. type = metaObject.getSetterType(propertyName);
  8. }
  9. try {
  10. //如果是集合
  11. if (objectFactory.isCollection(type)) {
  12. // 创建一个空集合
  13. propertyValue = objectFactory.create(type);
  14. // 设置集合属性
  15. metaObject.setValue(propertyName, propertyValue);
  16. // 返回集合属性的引用
  17. return propertyValue;
  18. }
  19. } catch (Exception e) {
  20. throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e);
  21. }
  22. } else if (objectFactory.isCollection(propertyValue.getClass())) {
  23. return propertyValue;
  24. }
  25. return null;
  26. }

那么list集合里面的元素又是在什么时候放进去的呢?这是在DefaultResultSetHandler的applyNestedResultMappings中拿到

行值rowValue后,才进行添加的。

DefaultResultSetHandler的applyNestedResultMappings的部分代码:

  1. boolean knownValue = rowValue != null;
  2. instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
  3. if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
  4. rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
  5. if (rowValue != null && !knownValue) {
  6. linkObjects(metaObject, resultMapping, rowValue);
  7. foundValues = true;
  8. }
  9. }
  1. linkObjects方法:
  1. private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
  2. final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
  3. if (collectionProperty != null) {
  4. final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
  5. // 集合添加元素
  6. targetMetaObject.add(rowValue);
  7. } else {
  8. metaObject.setValue(resultMapping.getProperty(), rowValue);
  9. }
  10. }

这样就完成了属性的赋值

控制台的l输出,可以参考一下:

MyBatis 是怎么把查询出来的结果集映射到实体类(JavaBean)上的,原理解释

MyBatis 是怎么把查询出来的结果集映射到实体类(JavaBean)上的,原理解释:

相信有很多开发者对MyBatis并不陌生,但谈起MyBatis底层到底是怎么工作的,没个两三年的工程师并非能完全掌握到手,读完以下这篇文章,相信你很快能理解MyBatis是怎么通过JDBC的封装,得到结果集并将结果集映射到JavaBean实体上的一个过程,在这里就不唠叨套多,先来了解下原理。

Mybatis3源码分析(16)-Sql解析执行-结果集映射(ResultSetHandler)
MyBatis查询结果集映射到JavaBean原理浅谈

相关文章