Mybatis原理:结果集封装详解

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

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

List<E>

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

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

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

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

下面开始进入主题:

一、数据准备

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

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

2.实体类

public class User implements Serializable{
    private String id;
    private String username;
    private String password;
    private Integer isValid;
    //一个用户,对应多篇博客
    private List<Blog> blogs;
}
public class Blog implements Serializable{
    private String id;
    private String title;
    private String userId;
}

3.mapper接口

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

4.测试方法

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

二、结果对象的创建

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

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

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

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    // 要返回的结果
    final List<Object> multipleResults = new ArrayList<>();
    // 迭代变量,结果集的个数
    int resultSetCount = 0;
    // 获取第一个结果集,并包装成ResultSetWrapper对象,
    // ResultSetWrapper对象含有已映射和未映射的列名和属性的对应关系
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取所有的ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    // ResultMap的个数
    int resultMapCount = resultMaps.size();
    // 校验:如果结果集有数据,但是没有定义返回的结果类型,就会报错
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      // 依次获取ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 处理结果集,这里是重点
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 获取下一个结果集
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
 
    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);
  }

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

// 处理结果集,这里是重点
      handleResultSet(rsw, resultMap, multipleResults, null);
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 {
        if (resultHandler == null) {
          // 如果结果处理器为空,则使用默认的结果处理器,没有自定义的情况下,都是走这个流程
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          // 处理每一行的值
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          // 将处理结果放到list集中
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

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

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

查看详细的 handleRowValues 方法:

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 如果有嵌套的ResultMap
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      // 处理含有嵌套ResultMap的结果
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 处理不含有嵌套ResultMap的结果
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

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

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

这个方法比较长:

private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // 跳过已处理的行
    skipRows(resultSet, rowBounds);
    Object rowValue = previousRowValue;
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      Object partialObject = nestedResultObjects.get(rowKey);
      // issue #577 && #542
      if (mappedStatement.isResultOrdered()) {
        if (partialObject == null && rowValue != null) {
          nestedResultObjects.clear();
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      } else {
        // 看这行代码,获取行值
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        if (partialObject == null) {
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
      }
    }
    if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
      previousRowValue = null;
    } else if (rowValue != null) {
      previousRowValue = rowValue;
    }
  }

我们找重点看吧:

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

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

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
    // resultMap的唯一标志id
    final String resultMapId = resultMap.getId();
    // 返回值
    Object rowValue = partialObject;
    if (rowValue != null) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      putAncestor(rowValue, resultMapId);
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
      ancestorObjects.remove(resultMapId);
    } else {
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
      // 创建结果对象
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
      // 行值不为空,并且结果对象有类型处理器
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        // 将行值包装成元数据对象MetaObject
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        // 是否使用自动映射,因为我们自定义了resultMap,所以不会走这个
        if (shouldApplyAutomaticMappings(resultMap, true)) {
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // 给对象的属性赋值
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        putAncestor(rowValue, resultMapId);
        // 给嵌套的结果属性赋值
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
        ancestorObjects.remove(resultMapId);
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
      }
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
        nestedResultObjects.put(combinedKey, rowValue);
      }
    }
    return rowValue;
  }

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

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

详细如下:

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);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        // issue gcode #109 && issue #149
        if (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 result
    return resultObject;
  }

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

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

三、属性赋值

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

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

1.普通属性赋值

// 给对象的属性赋值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
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();
        if (property == null) {
          continue;
        } else if (value == 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;
  }

这里也就是分3步:

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

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

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

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

回到 DefaultResultSetHandler的getRowValue 方法

// 给嵌套的结果属性赋值
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
    boolean foundValues = false;
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
        try {
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
          final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
          if (resultMapping.getColumnPrefix() == null) {
            // try to fill circular reference only when columnPrefix
            // is not specified for the nested result map (issue #215)
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
            if (ancestorObject != null) {
              if (newObject) {
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
              }
              continue;
            }
          }
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
          Object rowValue = nestedResultObjects.get(combinedKey);
          boolean knownValue = rowValue != null;
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
            if (rowValue != null && !knownValue) {
              linkObjects(metaObject, resultMapping, rowValue);
              foundValues = true;
            }
          }
        } catch (SQLException e) {
          throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
        }
      }
    }
    return foundValues;
  }

我们着重看这句:

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

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

private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
    final String propertyName = resultMapping.getProperty();
    Object propertyValue = metaObject.getValue(propertyName);
    if (propertyValue == null) {
      Class<?> type = resultMapping.getJavaType();
      if (type == null) {
        type = metaObject.getSetterType(propertyName);
      }
      try {
        //如果是集合
        if (objectFactory.isCollection(type)) {
          // 创建一个空集合
          propertyValue = objectFactory.create(type);
          // 设置集合属性
          metaObject.setValue(propertyName, propertyValue);
          // 返回集合属性的引用
          return propertyValue;
        }
      } catch (Exception e) {
        throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
      }
    } else if (objectFactory.isCollection(propertyValue.getClass())) {
      return propertyValue;
    }
    return null;
  }

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

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

DefaultResultSetHandler的applyNestedResultMappings的部分代码:

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

这样就完成了属性的赋值

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

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

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

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

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

相关文章