跳到主要内容
  1. 所有文章/
  2. 《Mybatis3源码深度解析》笔记/

Mybatis源码-级联映射和懒加载

·📄 8953 字·🍵 18 分钟

MyBatis其中一个比较强大的功能是支持查询结果级联映射。使用MyBatis级联映射,我们可以很轻松地实现一对多、一对一或者多对多关联查询,甚至可以利用MyBatis提供的级联映射实现懒加载。

所谓的懒加载,就是当我们在一个实体对象中关联其他实体对象时,如果不需要获取被关联的实体对象,则不需要为被关联的实体执行额外的查询操作,仅当调用当前实体的Getter方法获取被关联实体对象时,才会执行一次额外的查询操作。 通过这种方式在一定程度上能够减轻数据库的压力。

级联映射 #

一对多 #

<resultMap id="detailMap" type="com.blog4java.mybatis.example.entity.User">
    <collection property="orders" ofType="com.blog4java.mybatis.example.entity.Order"
                select="com.blog4java.mybatis.example.mapper.OrderMapper.listOrdersByUserId"
                javaType="java.util.ArrayList"
                column="id">
    </collection>
</resultMap>

一对一 #

<resultMap  id="detailNestMap" type="com.blog4java.mybatis.example.entity.Order">
    <id column="id" property="id"></id>
    <result column="createTime" property="createTime"></result>
    <result column="userId" property="userId"></result>
    <result column="amount" property="amount"></result>
    <result column="orderNo" property="orderNo"></result>
    <result column="address" property="address"></result>
    <association property="user"  javaType="com.blog4java.mybatis.example.entity.User" >
        <id column="userId" property="id"></id>
        <result column="name" property="name"></result>
        <result column="createTime" property="createTime"></result>
        <result column="password" property="password"></result>
        <result column="phone" property="phone"></result>
        <result column="nickName" property="nickName"></result>
    </association>
</resultMap>

鉴别器 #

 <resultMap id="detailMapForDiscriminator" type="com.blog4java.mybatis.example.entity.User">
    <discriminator javaType="String" column="gender">
        <!--当查询用户信息时,如果用户性别为女性,则获取用户对应的订单信息,如果用户性别为男性,则不获取订单信息。-->
        <case value="female" resultType="com.blog4java.mybatis.example.entity.User">
            <collection property="orders" ofType="com.blog4java.mybatis.example.entity.Order"
                        select="com.blog4java.mybatis.example.mapper.OrderMapper.listOrdersByUserId"
                        javaType="java.util.ArrayList"
                        column="id">
            </collection>
        </case>
    </discriminator>
</resultMap>

懒加载机制 #

MyBatis的级联映射可以通过两种方式实现:

  1. 为实体的属性关联一个外部的查询Mapper,这种情况下,MyBatis实际上为实体的属性执行一次额外的查询操作;
  2. 通过JOIN查询来实现,这种方式需要为实体关联的其他实体对应的属性配置映射关系,通过JOIN查询方式只需要一次查询即可。

在一些情况下,我们需要按需加载,即当我们查询用户信息时,如果不需要获取用户订单信息,则不需要执时订单查询对应的Mapper,仅当调用Getter方法获取订单数据时,才执行一次额外的查询操作。 这种方式能够在一定程度上能够减少数据库IO次数,提升系统性能。懒加载机制的作用。

<settings>
    <!-- 打开延迟加载的开关 -->
    <setting name="lazyLoadingEnabled" value="true" />
    <!-- 将积极加载改为懒加载即按需加载 -->
    <setting name="aggressiveLazyLoading" value="false" />
    <!-- toString,hashCode等方法不触发懒加载 -->
    <setting name="lazyLoadTriggerMethods" value=""/>
</settings>
public void testLazyQuery() {
    Order order =  orderMapper.getOrderByNo("order_2314234");
    System.out.println("完成Order数据查询");
    // 调用getUser()方法时执行懒加载
    order.getUser();
}

级联映射实现原理 #

生成ResultMap类 #

MyBatis通过ResultMap类描述<resultMap>标签的配置信息,ResultMap类的所有属性如下:

public class ResultMap {
  private Configuration configuration;
  // <resultMap>标签id属性
  private String id;
  // <resultMap>标签type属性
  private Class<?> type;
  // <result>标签配置的映射信息
  private List<ResultMapping> resultMappings;
  // <id>标签配置的主键映射信息
  private List<ResultMapping> idResultMappings;
  // <constructor>标签配置的构造器映射信息
  private List<ResultMapping> constructorResultMappings;
  // <result>标签配置的结果集映射信息
  private List<ResultMapping> propertyResultMappings;
  // 存放所有映射的数据库字段信息,当使用columnPrefix配置字段前缀时,所有字段都会追加前缀
  private Set<String> mappedColumns;
  // 存放所有映射的属性信息
  private Set<String> mappedProperties;
  // <discriminator>标签配置的鉴别器信息
  private Discriminator discriminator;
  // 是否有嵌套的<resultMap>
  private boolean hasNestedResultMaps;
  // 是否有存在嵌套查询
  private boolean hasNestedQueries;
  // autoMapping属性值,是否自动映射
  private Boolean autoMapping;
  // ...

}

MyBatis中的Mapper配置信息解析都是通过XMLMapperBuilder类完成的,该类提供了一个parse()方法,用于解析Mapper中的所有配置信息:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // 调用XPathParser的evalNode()方法获取根节点对应的XNode对象
    configurationElement(parser.evalNode("/mapper"));
    // 將资源路径添加到Configuration对象中
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }
  // 继续解析之前解析出现异常的ResultMap对象
  parsePendingResultMaps();
  // 继续解析之前解析出现异常的CacheRef对象
  parsePendingCacheRefs();
  // 继续解析之前解析出现异常<select|update|delete|insert>标签配置
  parsePendingStatements();
}

private void configurationElement(XNode context) {
  try {
    // 获取命名空间
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 设置当前正在解析的Mapper配置的命名空间
    builderAssistant.setCurrentNamespace(namespace);
    // 解析<cache-ref>标签
    cacheRefElement(context.evalNode("cache-ref"));
    // 解析<cache>标签
    cacheElement(context.evalNode("cache"));
    // 解析所有的<parameterMap>标签
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 解析所有的<resultMap>标签
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 解析所有的<sql>标签
    sqlElement(context.evalNodes("/mapper/sql"));
    // 解析所有的<select|insert|update|delete>标签
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}


private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  String id = resultMapNode.getStringAttribute("id",
      resultMapNode.getValueBasedIdentifier());
  // 属性优先级按照type->ofType->resultType->javaType顺序,type属性为空或者无type属性,则使用ofType属性,依此类推
  String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
          resultMapNode.getStringAttribute("resultType",
              resultMapNode.getStringAttribute("javaType"))));
  // 是否继承ResultMap
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  Class<?> typeClass = resolveClass(type);
  Discriminator discriminator = null;
  // 参数映射列表
  List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
  resultMappings.addAll(additionalResultMappings);
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    if ("constructor".equals(resultChild.getName())) {
      // 解析<constructor>标签
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {
      // 解析<discriminator>标签
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<ResultFlag>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  // 通过ResultMapResolver对象构建ResultMap对象
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    return resultMapResolver.resolve();
  } catch (IncompleteElementException  e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

public ResultMap resolve() {
  return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}

如上面的代码所示,在XMLMapperBuilder 类的resultMapElement()方法中,首先获取<resultMap>标签的所有属性信息,然后对<id>、<constructor>、<discriminator>子标签进行解析,接着创建一个ResultMapResolver对象,调用ResultMapResolver 对象的resolve()方法返回一个ResultMap对象。

public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);

    if (extend != null) {
      // 如果继承了其他ResultMap
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      // 获取继承的父ResultMap对象
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // 如果父ResultMap定义了构造器映射,则移除构造器映射.
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      // 將父ResultMap配置的映射信息添加到当前ResultMap
      resultMappings.addAll(extendedResultMappings);
    }
    // 通过建造者模式创建ResultMap对象
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }

处理结果集 #

在DefaultResultSetHandler 类的handleResultSets()方法中,为了简化对JDBC 中 ResultSet 对象的操作,将ResultSet 对象包装成ResultSetWrapper 对象,然后获取MappedStatement对象对应的ResultMap对象,接着调用重载的handleResultSet()方法进行处理。该方法代码实现如下:

// org.apache.ibatis.executor.statement.PreparedStatementHandler#query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  // 调用PreparedStatement对象的execute()方法,执行SQL语句
  ps.execute();
  // 调用ResultSetHandler的handleResultSets()方法处理结果集
  return resultSetHandler.<E> handleResultSets(ps);
}

// org.apache.ibatis.executor.resultset.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<Object>();
  int resultSetCount = 0;
  // 1、获取ResultSet对象,將ResultSet对象包装为ResultSetWrapper
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 2、获取ResultMap信息,一般只有一个ResultMap
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  // 校验ResultMap,如果该ResultMap名称没有配置,则抛出异常
  validateResultMapsCount(rsw, resultMapCount);
  // 如果指定了多个ResultMap,则对每个ResultMap进行处理
  while (rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 3、调用handleResultSet方法处理结果集
    handleResultSet(rsw, resultMap, multipleResults, null);
    // 获取下一个结果集对象,需要JDBC驱动支持多结果集
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }
  // 如果JDBC驱动支持多结果集,可以通过<select>标签resultSets属性指定多个ResultMap
  // 处理<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方法处理结果集
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }
  // 对multipleResults进行处理,如果只有一个结果集,则返回结果集中的元素,否则返回多个结果集
  return collapseSingleResultList(multipleResults);
}

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    //  仅当指定了<select>标签resultSets属性,parentMapping值才不为null
    if (parentMapping != null) {
      // 调用handleRowValues()方法处理
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      if (resultHandler == null) {
        // 如果未指定ResultHandler,则创建默认的ResultHandler实现
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        // 调用handleRowValues()方法处理
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        // 获取处理后的结果
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    closeResultSet(rsw.getResultSet());
  }
}


public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 是否有嵌套ResultMap
    if (resultMap.hasNestedResultMaps()) {
      // 嵌套查询校验RowBounds,可以通过设置safeRowBoundsEnabled=false参数绕过校验
      ensureNoRowBounds();
      // 校验ResultHandler,可以设置safeResultHandlerEnabled=false参数绕过校验
      checkResultHandler();
      // 如果有嵌套的ResultMap,调用handleRowValuesForNestedResultMap处理嵌套ResultMap
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 如果无嵌套的ResultMap,调用handleRowValuesForSimpleResultMap处理简单非嵌套ResultMap
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

如上面的代码所示,在DefaultResultSetHandler类的handleRowValues()方法中判断ResultMap中是否有嵌套的ResultMap,当使用<association>或<collection>标签通过JOIN查询方式进行级联映射时,hasNestedResultMaps()方法的返回值为true。如果有嵌套的ResultMap,则调用handleRowValuesForNestedResultMap()方法进行处理,否则调用handleRowValuesForSimpleResultMap()方法。

我们先来看一下有嵌套ResultMap时的处理逻辑。handleRowValuesForNestedResultMap()方法的代码如下:

private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  final DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
  // 调用skipRows()方法將ResultSet对象定位到rowBounds对象指定的偏移量
  skipRows(rsw.getResultSet(), rowBounds);
  // previousRowValue为上一个结果对象
  Object rowValue = previousRowValue;
  // 遍历处理每一行记录
  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    // 处理<discriminator>标签配置的鉴别器
    final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
    final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
    // 获取缓存的嵌套实体
    Object partialObject = nestedResultObjects.get(rowKey);
    if (mappedStatement.isResultOrdered()) {
      // 缓存的嵌套实体对象不为空
      if (partialObject == null && rowValue != null) {
        nestedResultObjects.clear();
        storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
      }
      // 调用getRowValue
      rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
    } else {
      rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      if (partialObject == null) {
        storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
      }
    }
  }
  if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    previousRowValue = null;
  } else if (rowValue != null) {
    previousRowValue = rowValue;
  }
}

上面的代码中,对结果集对象进行遍历,处理每一行数据。首先调用resolveDiscriminatedResultMap()方法处理<resultMap>标签中通过<discriminator>标签配置的鉴别器信息,根据字段值获取对应的ResultMap对象,然后调用DefaultResultSetHandler类的getRowValue()方法将结果集中的一行数据转换为Java实体对象。下面是getRowValue()方法的代码:

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
  final String resultMapId = resultMap.getId();
  // 如果缓存了嵌套ResultMap对应的实体对象,则调用applyNestedResultMappings()方法处理
  Object rowValue = partialObject;
  if (rowValue != null) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    putAncestor(rowValue, resultMapId);
    // 调用applyNestedResultMappings()方法处理嵌套的映射
    applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
    ancestorObjects.remove(resultMapId);
  } else {
    // ResultLoaderMap用于存放懒加载ResultMap信息
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 处理通过<constructor>标签配置的构造器映射
    rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    // 判断结果对象是否注册对应的TypeHandler
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      // 是否使用构造器映射
      boolean foundValues = this.useConstructorMappings;
      // 是否指定了自动映射
      if (shouldApplyAutomaticMappings(resultMap, true)) {
        // 调用applyAutomaticMappings()方法处理自动映射
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      // 处理非<id>,<constructor>指定的映射
      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;
}

在getRowValue()方法中,主要做了以下几件事情:

  1. 调用createResultObject()方法处理通过<constructor>标签配置的构造器映射,根据配置信息找到对应的构造方法,然后通过MyBatis中的ObjectFactory创建ResultMap关联的实体对象。

  2. 调用applyAutomaticMappings()方法处理自动映射,对未通过<result>标签配置映射的数据库字段进行与Java实体属性的映射处理。该方法的具体实现代码如下:

    首先获取未指定映射的所有数据库字段和对应的Java属性,然后获取对应的字段值,通过反射机制为Java实体对应的属性值赋值。

    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);
            // 对所有ResultMap映射信息进行遍历,获取嵌套的ResultMap,然后为嵌套ResultMap对应的实体属性设置值
            final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
            if (resultMapping.getColumnPrefix() == null) {
              // 当未指定columnPrefix属性时,从缓存中获取嵌套的ResultMap对应的Java实体对象,避免循环引用问题
              Object ancestorObject = ancestorObjects.get(nestedResultMapId);
              if (ancestorObject != null) {
                if (newObject) {
                  // 调用linkObjects方法將將外层实体对象和嵌套对象进行关联
                  linkObjects(metaObject, resultMapping, ancestorObject);
                }
                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)) {
              // 调用getRowValue()方法,根据嵌套结果集映射信息创建Java实体
              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;
    }
    
  3. 用applyPropertyMappings()方法处理<result>标签配置的映射信息。该方法处理逻辑相对简单,对所有<result>标签配置的映射信息进行遍历,然后找到数据库字段对应的值,为Java实体属性赋值。applyPropertyMappings()方法的实现代码如下:

    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
          throws SQLException {
      // 获取通过<result>标签指定映射的字段名称
      final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
      // foundValues变量用于标识是否获取到数据库字段对应的值
      boolean foundValues = false;
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      // 对所有通过<result>标签配置了映射的字段进行赋值
      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) {
          // 获取数据库字段对应的值
          Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
          // 获取Java实体对应的属性名称
          final String property = propertyMapping.getProperty();
          if (property == null) {
            continue;
          } else if (value == DEFERED) {
            foundValues = true;
            continue;
          }
          if (value != null) {
            foundValues = true;
          }
          if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
            // 调用MetaObject对象的setValue()方法为返回的实体对象设置属性值
            metaObject.setValue(property, value);
          }
        }
      }
      return foundValues;
    }
    

如上面的代码所示,在applyNestedResultMappings()方法中,首先获取嵌套ResultMap对象,然后根据嵌套ResultMap的Id从缓存中获取嵌套ResultMap对应的Java实体对象,如果能获取到,则调用 linkObjects()方法将嵌套Java 实体与外部Java 实体进行关联。如果缓存中没有,则调用getRowValue()方法创建嵌套ResultMap对应的Java实体对象并进行属性映射,然后调用linkObjects()方法与外部的Java实体对象进行关联。

懒加载实现原理 #

MyBatis支持通过<collection>或者<association>标签关联一个外部的查询Mapper,当通过MyBatis配置开启懒加载机制时,执行查询操作不会触发关联的查询Mapper,而通过Getter方法访问实体属性时才会执行一次关联的查询Mapper,然后为实体属性赋值。

在DefaultResultSetHandler类的handleRowValues()方法中处理结果集时,对嵌套的ResultMap和非嵌套ResultMap做了不同处理,代码如下:

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  // 是否有嵌套ResultMap
  if (resultMap.hasNestedResultMaps()) {
    // 嵌套查询校验RowBounds,可以通过设置safeRowBoundsEnabled=false参数绕过校验
    ensureNoRowBounds();
    // 校验ResultHandler,可以设置safeResultHandlerEnabled=false参数绕过校验
    checkResultHandler();
    // 如果有嵌套的ResultMap,调用handleRowValuesForNestedResultMap处理嵌套ResultMap
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    // 如果无嵌套的ResultMap,调用handleRowValuesForSimpleResultMap处理简单非嵌套ResultMap
    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<Object>();
  skipRows(rsw.getResultSet(), rowBounds);
  // 遍历处理每一行记录
  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    // 对<discriminator>标签配置的鉴别器进行处理,获取实际映射的ResultMap对象
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
    // 调用getRowValue()把一行数据转换为Java实体对象
    Object rowValue = getRowValue(rsw, discriminatedResultMap);
    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  }
}

// 处理非嵌套ResultMap
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  // 创建ResultLoaderMap对象,用于存放懒加载属性信息
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  // 创建ResultMap指定的类型实例,通常为<resultMap>标签的type属性指定的类型
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
  // 判断该类型是否注册了TypeHandler
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    // 判断是否需要处理自动映射
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      // 调用applyAutomaticMappings()方法处理自动映射的字段
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    // 处理<result>标签配置映射的字段
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

如上面的代码所示,在getRowValue()方法中主要做了下面几件事情:

  1. 创建ResultLoaderMap对象,该对象用于存放懒加载的属性及对应的ResultLoader对象,MyBatis中的ResultLoader用于执行一个查询Mapper,然后将执行结果赋值给某个实体对象的属性。
  2. 调用createResultObject()方法创建ResultMap对应的Java实体对象,我们需要重点关注该方法的实现,代码如下:
    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<Class<?>>();
        final List<Object> constructorArgs = new ArrayList<Object>();
        // 调用createResultObject()方法创建结果对象
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
          // 获取<result>结果集映射信息
          final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
          for (ResultMapping propertyMapping : propertyMappings) {
            // 如果映射中配置了懒加载,则创建代理对象
            if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
              // 调用ProxyFactory实例的createProxy()方法创建代理对象
              resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
              break;
            }
          }
        }
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
      }
    
  3. 用applyAutomaticMappings()方法处理自动映射。
  4. 调用applyPropertyMappings()方法处理<result>标签配置的映射字段,该方法中除了为Java实体属性设置值外,还将指定了懒加载的属性添加到ResultLoaderMap对象中。当我们开启懒加载时,执行查询Mapper返回的实际上是通过Cglib或Javassist创建的动态代理对象。假设我们指定了使用Cglig创建动态代理对象,调用动态代理对象的Getter方法时会执行MyBatis中定义的拦截逻辑,代码如下:
    // org.apache.ibatis.executor.loader.cglib.CglibProxyFactory.EnhancedResultObjectProxyImpl#intercept
    public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        final String methodName = method.getName();
        try {
          synchronized (lazyLoader) {
            if (WRITE_REPLACE_METHOD.equals(methodName)) {
              Object original;
              if (constructorArgTypes.isEmpty()) {
                original = objectFactory.create(type);
              } else {
                original = objectFactory.create(type, constructorArgTypes, constructorArgs);
              }
              PropertyCopier.copyBeanProperties(type, enhanced, original);
              if (lazyLoader.size() > 0) {
                return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
              } else {
                return original;
              }
            } else {
              if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                  lazyLoader.loadAll();
                } else if (PropertyNamer.isSetter(methodName)) {
                  final String property = PropertyNamer.methodToProperty(methodName);
                  lazyLoader.remove(property);
                } else if (PropertyNamer.isGetter(methodName)) {
                  final String property = PropertyNamer.methodToProperty(methodName);
                  if (lazyLoader.hasLoader(property)) {
                    lazyLoader.load(property);
                  }
                }
              }
            }
          }
          return methodProxy.invokeSuper(enhanced, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
    }
    
    在EnhancedResultObjectProxyImpl类的intercept()方法中,获取Getter方法对应的属性名称,然后调用ResultLoaderMap对象的hasLoader()方法判断该属性是否是懒加载属性,如果是,则调用ResultLoaderMap对象的load()方法加载该属性,ResultLoaderMap对象的load()方法最终会调用LoadPair.load()
    public void load() throws SQLException {
      /* These field should not be null unless the loadpair was serialized.
       * Yet in that case this method should not be called. */
      if (this.metaResultObject == null) {
        throw new IllegalArgumentException("metaResultObject is null");
      }
      if (this.resultLoader == null) {
        throw new IllegalArgumentException("resultLoader is null");
      }
    
      this.load(null);
    }
    
    public void load(final Object userObject) throws SQLException {
      if (this.metaResultObject == null || this.resultLoader == null) {
        if (this.mappedParameter == null) {
          throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                  + "required parameter of mapped statement ["
                  + this.mappedStatement + "] is not serializable.");
        }
    
        final Configuration config = this.getConfiguration();
        final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
        if (ms == null) {
          throw new ExecutorException("Cannot lazy load property [" + this.property
                  + "] of deserialized object [" + userObject.getClass()
                  + "] because configuration does not contain statement ["
                  + this.mappedStatement + "]");
        }
    
        this.metaResultObject = config.newMetaObject(userObject);
        this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
      }
    
      /* We are using a new executor because we may be (and likely are) on a new thread
       * and executors aren't thread safe. (Is this sufficient?)
       *
       * A better approach would be making executors thread safe. */
      if (this.serializationCheck == null) {
        final ResultLoader old = this.resultLoader;
        this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
                old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
      }
    
      this.metaResultObject.setValue(property, this.resultLoader.loadResult());
    }
    
    该方法中创建了一个ResultLoader 对象,然后ResultLoader 对象的loadResult()方法执行查询操作,将查询结果赋值给对应的Java 实体属性。ResultLoader类的loadResult()方法实现如下:
    public Object loadResult() throws SQLException {
      List<Object> list = selectList();
      resultObject = resultExtractor.extractObjectFromList(list, targetType);
      return resultObject;
    }
    
    private <E> List<E> selectList() throws SQLException {
      Executor localExecutor = executor;
      if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
        localExecutor = newExecutor();
      }
      try {
        return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
      } finally {
        if (localExecutor != executor) {
          localExecutor.close(false);
        }
      }
    }
    
    如上面的代码所示,在ResultLoader类的loadResult()方法中调用selectList()方法完成查询操作,selectList()方法中最终会调用Executor对象的query()方法完成查询操作。