从根上理解:Mybatis中数据库的列和Java字段是怎么映射的?(补)

1. 写在前头

大家好,我是方圆,这篇帖子是继从根上理解:Mybatis中数据库的列和Java字段是怎么映射的之后的补充,上一篇帖子我们只在源码层说明了ResultMap是如何被解析的,而没有从具体SQL执行层面上分析查询出的结果集是如何封装的,所以这篇帖子就来解决这个问题,两篇合起来,才是它的完整版,废话不多说,开始了

2. 准备工作

  • 需要用到的实体类,非常的简单,只有id, name, tel三个字段
public class Dept {

    private Integer id;

    private String name;

    private String tel;
}
复制代码
  • 对应的xml文件,定义简单的select语句ResultMap
    <resultMap id="deptMap" type="Dept">
        <id property="id" column="id"/>
        <id property="name" column="name"/>
        <id property="tel" column="tel"/>
    </resultMap>

    <select id="findAll" resultMap="deptMap">
        select * from dept
    </select>
复制代码
  • 之后我们Debug的测试用例都是采用findAll方法

3. 从SimpleExecutor的doQuery方法开始

  • 执行查询动作的代码在这里,由StatementHandlerquery方法执行查询
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      // StatementHandler执行查询
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
复制代码
  • 我们进入它的query方法看看
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 请求数据库查询
    ps.execute();
    // 这个是需要我们重点看的方法,这个方法会处理返回的结果集
    return resultSetHandler.handleResultSets(ps);
  }
复制代码

3.1 handleResultSets方法

:为了可读性,我会把存储过程相关的源码全部去掉,因为这个特性实在是用得少,加上又影响文章的流畅,所以大家发现...的时候就说明这个地方我略过了无关的代码

  • 我们把这个方法拉出来看看,关键的代码行我都标注了章节号,对应着看
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;
    // 3.1.1 将ResultSet封装成ResultSetWrapper
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    // 3.1.2 获取我们定义的ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 3.1.3 处理返回的resultSet结果集
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    
    // 存储过程相关...

    // 3.1.4 返回我们需要的查询结果
    return collapseSingleResultList(multipleResults);
  }
复制代码
  • 主方法中的源码看起来还是很清晰
  1. 首先会对ResultSet进行装饰
  2. 之后获取我们定义的ResultMap并处理结果集
  3. 最后会将我们想要的查询结果返回
  • 下面看源码的过程中,我会把执行findAll方法的Debug过程也加入进来,我觉得这样有具体的例子更好理解

3.1.1 getFirstResultSet方法,将ResultSet封装成ResultSetWrapper

  • 还是一样,我们也把getFirstResultSet方法拿出来看看
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    
    // 存储过程相关...
    
    // 可以发现,只是在这里调用了ResultSetWrapper的构造方法
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }
复制代码
  • 还是很简单,没有复杂的操作,ResultSet之后,调用了ResultSetWrapper的构造方法,我们直接看源码
  // 会将元数据列名、java类名和jdbc类型初始化在list中,并且一一对应
  private final List<String> columnNames = new ArrayList<>();
  private final List<String> classNames = new ArrayList<>();
  private final List<JdbcType> jdbcTypes = new ArrayList<>();

  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));
    }
  }
复制代码
  • 这里的操作非常简单,我把debug截图放出来就非常容易懂了

在这里插入图片描述

  • 因为list插入是有序的,所以列的信息会一一对应,那么在这里初始化这些信息之后就可以拿来直接用,用空间换时间的模式

3.1.2 获取我们定义的ResultMap

  • 这个步骤非常的简单,我就如直接上图了

在这里插入图片描述

  • 我们可以很容易的发现图上红框内的部分都是我们在xml文件中指定的resultMap中的东西
    <resultMap id="deptMap" type="Dept">
        <id property="id" column="id"/>
        <id property="name" column="name"/>
        <id property="tel" column="tel"/>
    </resultMap>
复制代码

3.1.3 handleResultSet方法,处理返回的resultSet结果集

  • 这里会对resultSet进行处理,我们直接看else部分,这里会
  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则走这里
        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 {
      closeResultSet(rsw.getResultSet());
    }
  }
复制代码
  • 多数情况下resultHandler都是null,我们进行debug的样例也是一样,都会执行的是使用默认defaultResultHandler的代码(附图为执行selectList时resultHandler这个参数就是null)

在这里插入图片描述

  • 接下来我们还是要继续看handleRowValues方法,它的具体执行部分
3.1.3.1 handleRowValues方法
  • 这里会判断resultMap里是否还包含resultMap,根据结果走不同的逻辑,我们定义的resultMap比较简单,所以走的是下面没有嵌套的逻辑
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 是否有嵌套的resultMap,有则执行这里
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 没有嵌套则执行这里
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }
复制代码
  • 我们接着看handleRowValuesForSimpleResultMap方法
3.1.3.2 handleRowValuesForSimpleResultMap方法
  • 下边出现了resultSet,这也就意味着真正的,注释标记好了步骤,其中我们需要重点关注标注*号的两行代码,获取行数据并封装为对象,和之后的保存动作
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    // 这里终于出现了resultSet
    ResultSet resultSet = rsw.getResultSet();
    // 内存分页相关,略过
    skipRows(resultSet, rowBounds);
    // 循环封装返回结果
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      // 鉴别器相关,鉴别器的作用是判断使用哪个resultMap,大家可以去了解一下
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // *获取行数据并封装为对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // *封装好的对象保存到集合中
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }
复制代码
3.1.3.3 getRowValue方法,获取行数据
  • 这个方法调用层级还蛮多的,但是非常简单,我们就在这一小节内都把它说完,标注*号的要重点关注
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // *根据结果集类型创建一个空对象(空的实体类)
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      // 创建元对象,之后会用反射来来为属性赋值
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      // *处理自动映射,解释了在我们没有指定resultMap时,依然能完成属性值和列值映射的原因
      // (比如指定resultType的时候)
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      // *处理resultMap中配置好的映射
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      // 如果整个过程没有映射到任何属性,则根据配置决定返回空对象还是null
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }
复制代码
  • 创建空对象的createResultObject方法,我只暴露出了核心代码,其他复杂的杂七杂八省略掉了,结合注释阅读
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    ...
    
    // 核心逻辑
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    
    ...
    
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();
    return resultObject;
  }

  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
      throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();

    // 根据对象类型,有以下四种情况
    // 1. 定义了typeHandler,此处处理
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    // 2. 在resultMap中定义了<constructor>子标签
    } else if (!constructorMappings.isEmpty()) {
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    // 3. 有默认的无参构造器,则直接借助ObjectFactory创建
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
      return objectFactory.create(resultType);
    // 4. 没有默认构造器,则MyBatis会自己试探性寻找合适的构造方法创建对象
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
      return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
  }
复制代码

多数情况下走的都是第3种,无参构造方法构造对象,我们Debug的例子走的就是这种

  • 处理自动映射,执行applyAutomaticMappings方法

这里我插播一条插曲:在Configuration配置中,有指定自动映射的方式,默认为AutoMappingBehavior.PARTIAL,这种方式会开启自动映射但是不能处理包含嵌套的映射

我们看看它的逻辑,分三个步骤:解析出自动映射获取值反射set值,如下

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    // 1. 解析出自动映射
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        // 2. 利用typehandler处理并获取到值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // 3. 利用反射set属性值
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
复制代码
  • 处理resultMap中配置好的列的映射,applyPropertyMappings方法,这个方法有点儿长,但是别担心,非常简单,看着注释一步步来就好
  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    // 获取到我们在resultMap指定的列名们
    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) {
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {

        // 1. 在resultSet中取值
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // 2. 取出对应的属性(还记得上面我们截图的那三个list吗?这里用了)
        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())) {
          // 3. 反射set值
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }
复制代码

这个方法的核心步骤和上边我们分析的自动映射相似,在resultSet中取值取出对应的属性反射set值,之后就将我们需要的数据封装在对象里了,之后就是将这个对象放入集合,我们接着往下看

3.1.3.4 封装好的对象保存到集合中,storeObject方法
  • 具体看源码中的注释即可
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);
    }
}

// 这个方法分为两步,我们在下面看
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    // 1.对象存入resultContext
    resultContext.nextResultObject(rowValue);
    // 2. 将对象存入list
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
  // 对应1.
  public void nextResultObject(T resultObject) {
    // 内存分页相关,略过
    resultCount++;
    // 这里是直接把封装好的对象存入了resultContext
    this.resultObject = resultObject;
  }

  // 对应2.
  private final List<Object> list;

  public void handleResult(ResultContext<?> context) {
    // 这不是,直接把刚才放进去的对象拿出来存入了list中
    list.add(context.getResultObject());
  }
复制代码
3.1.3.5 不想忽视的步骤:multipleResults.add过程
  • 大家回到3.1.3节看一下,这个步骤是在处理完查询结果对象之后将结果加入到multipleResults列表里,而这个列表的字面意思是多个结果,这是针对存储过程而言的,我们没有使用存储过程,所以返回的结果只有一个,出现multipleResults列表中保存有一个结果列表的形式,如下图所示

在这里插入图片描述

  • 接着往下看能更好的理解这一点

3.1.4 collapseSingleResultList方法,返回我们需要的查询结果

  • 好了,这不就到了最后一步了嘛。注意这里判断了一下multipleResults的大小,结合前边我们刚说的,没使用存储过程,列表里只有一个对象,拿出来返回,如果是存储过程,就直接返回的是multipleResults
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
复制代码

3.1.5 番外 - 指定resultType时,映射的实现

  • 上文中我们有提到applyAutomaticMappings自动映射方法,我们debug看一下,在我们没有指定resultMap时,它是如何实现的列与属性的映射。我们在xml文件中指定好resultType
    <select id="findAll" resultType="Dept">
        select * from dept
    </select>
复制代码
  • 我们debug直接进来,发现autoMapping生成了我们没指定的列与属性的映射

在这里插入图片描述

  • 那么我们进入createAutomaticMappings方法看看,是怎么生成的,大家重点关注注释即可,别嫌代码长,看注释!
  private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    // 注意这个resultMap'平平无奇',它不包含任何映射关系,见下方图片
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    // 这里get的映射列表为null
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    
    if (autoMapping == null) {
      autoMapping = new ArrayList<>();
      // 获取到未映射的列名(id, name, tel)
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      for (String columnName : unmappedColumnNames) {
        // 重点:这里直接让属性名为列名
        String propertyName = columnName;

        // 拼接前缀
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
          if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
            propertyName = columnName.substring(columnPrefix.length());
          } else {
            continue;
          }
        }
        // 根据属性名找到对应的属性,并将列与属性的映射关系封装到autoMapping中
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
        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))) {
            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
            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;
  }
复制代码

在这里插入图片描述

  • 我们可以发现mybatis默认把属性名指定为了列名,再根据这个属性名再去匹配属性,成功匹配的生成autoMapping映射

  • 我们再回到applyAutomaticMappings方法中,再捋一下这个流程

  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) {
        // 获取值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // 反射set值
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
复制代码
  • 还是之前我们提到的老步骤,解析出自动映射获取值反射set值

4. Over

终于写完了,来回来去的将近写了一小天,也没那么难,如果你真的看完了,文中有不对和你觉得能完善的地方,非常欢迎提出来,好了,拜拜

Guess you like

Origin juejin.im/post/7076294053221892104