MyBatis - SQL执行过程(十四)ResultSetHandler

MyBatis - SQL执行过程(十四)ResultSetHandler

该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对各位读者会不太友好,阅读前需要对 MyBatis 和 Spring 有一定的了解。比较适合刚接触,会使用但是一直没去探究底层的同学。

MyBatis 版本:3.5.6

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

该系列其他文档请查看:《 MyBatis 系列 - 导读》

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

  • 《MyBatis - SQL执行过程(一)Executor》
  • 《SQL执行过程(二)之StatementHandler》
  • 《SQL执行过程(三)之ResultSetHandler》
  • 《SQL执行过程(四)之延迟加载》

MyBatis中SQL执行的整体过程如下图所示:

MyBatis - SQL执行过程(十四)ResultSetHandler_第1张图片

在 SqlSession 中,会将执行 SQL 的过程交由Executor执行器去执行,过程大致如下:

  1. 通过DefaultSqlSessionFactory创建与数据库交互的 SqlSession “会话”,其内部会创建一个Executor执行器对象
  2. 然后Executor执行器通过StatementHandler创建对应的java.sql.Statement对象,并通过ParameterHandler设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过KeyGenerator先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的ResultSet结果集对象包装成ResultSetWrapper,然后通过DefaultResultSetHandler对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(三)ResultSetHandler

DefaultResultSetHandler(结果集处理器)将数据库查询结果转换成 Java 对象是一个非常繁琐的过程,需要处理各种场景,如果继续往下看,请做好心理准备

可以先跳转到 DefaultResultSetHandler,查看流程图

在前面SQL执行过程一系列的文档中,已经详细地分析了在MyBatis的SQL执行过程中,SqlSession会话将数据库操作交由Executor执行器去完成,然后通过StatementHandler去执行数据库相关操作,并获取到数据库的执行结果

如果是数据库查询操作,则需要通过ResultSetHandler对查询返回的结果集进行映射处理,转换成对应的Java对象,算是SQL执行过程的最后一步,那么我们来看看MyBatis是如何完成这个繁杂的解析过程的

ResultSetHandler接口的实现类如下图所示:

MyBatis - SQL执行过程(十四)ResultSetHandler_第2张图片

先回顾一下ResultSetHandler在哪被调用,在PreparedStatementHandlerquery方法中,代码如下:

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行
    ps.execute();
    // 结果处理器并返回结果
    return resultSetHandler.handleResultSets(ps);
}
  • 属性resultSetHandler默认为DefaultResultSetHandler对象,可以回到《MyBatis - SQL执行过程(十三)StatementHandler》BaseStatementHandler小节中的构造方法的第3步可以看到
  • 调用resultSetHandlerhandleResultSets(Statement stmt)方法,对结果集进行映射,转换成Java对象并返回

ResultSetWrapper

因为在DefaultResultSetHandler中,对ResultSet的操作更多的是它的ResultSetWrapper包装类,所以我们先来看看这个类

org.apache.ibatis.executor.resultset.ResultSetWrapperjava.sql.ResultSet的包装类,为DefaultResultSetHandler提供许多便捷的方法,直接来看它的代码

构造方法
public class ResultSetWrapper {

  /**
   * ResultSet 对象
   */
  private final ResultSet resultSet;
  /**
   * 类型处理器注册表
   */
  private final TypeHandlerRegistry typeHandlerRegistry;
  /**
   * ResultSet 中每列的列名
   */
  private final List<String> columnNames = new ArrayList<>();
  /**
   * ResultSet 中每列对应的 Java Type
   */
  private final List<String> classNames = new ArrayList<>();
  /**
   * ResultSet 中每列对应的 Jdbc Type
   */
  private final List<JdbcType> jdbcTypes = new ArrayList<>();
  /**
   * 记录每列对应的 TypeHandler 对象
   * key:列名
   * value:TypeHandler 集合
   */
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
  /**
   * 记录了被映射的列名
   * key:ResultMap 对象的 id {@link #getMapKey(ResultMap, String)}
   * value:ResultMap 对象映射的列名集合
   */
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  /**
   * 记录了未映射的列名
   * key:ResultMap 对象的 id {@link #getMapKey(ResultMap, String)}
   * value:ResultMap 对象未被映射的列名集合
   */
  private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    // 获取 ResultSet 的元信息
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      // 获得列名或者通过 AS 关键字指定列名的别名
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      // 获得该列对应的 Jdbc Type
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      // 获得该列对应的 Java Type
      classNames.add(metaData.getColumnClassName(i));
    }
  }
}
  • resultSet:被包装的ResultSet结果集对象
  • typeHandlerRegistry:类型处理器注册表,因为需要进行Java Type与Jdbc Type之间的转换
  • columnNames:结果集中的所有列名
  • classNames:结果集中的每列的对应的Java Type的名称
  • jdbcTypes:结果集中的每列对应的Jdbc Type
  • typeHandlerMap:结果集中每列对应的类型处理器
  • mappedColumnNamesMap:保存每个ResultMap对象中映射的列名集合,也就是我们在标签下的子标签配置的column属性
  • unMappedColumnNamesMap:保存每个ResultMap对象中未映射的列名集合,也就是没有在标签下配置过,但是查询结果返回了

在构造方法中,会初始化上面的columnNamesclassNamesjdbcTypes属性

getTypeHandler方法

getTypeHandler(Class propertyType, String columnName):通过列名和Java Type获取对应的TypeHandler类型处理器,方法如下:

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
    TypeHandler<?> handler = null;
    // 获取列名对应的类型处理器
    Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
    if (columnHandlers == null) {
      columnHandlers = new HashMap<>();
      typeHandlerMap.put(columnName, columnHandlers);
    } else {
      handler = columnHandlers.get(propertyType);
    }
    if (handler == null) {
      // 获取该列对应的 Jdbc Type
      JdbcType jdbcType = getJdbcType(columnName);
      // 根据 Java Type 和 Jdbc Type 获取对应的 TypeHandler 类型处理器
      handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
      // Replicate logic of UnknownTypeHandler#resolveTypeHandler
      // See issue #59 comment 10
      if (handler == null || handler instanceof UnknownTypeHandler) {
        // 从 ResultSet 中获取该列对应的 Java Type 的 Class 对象
        final int index = columnNames.indexOf(columnName);
        final Class<?> javaType = resolveClass(classNames.get(index));
        if (javaType != null && jdbcType != null) {
          handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
        } else if (javaType != null) {
          handler = typeHandlerRegistry.getTypeHandler(javaType);
        } else if (jdbcType != null) {
          handler = typeHandlerRegistry.getTypeHandler(jdbcType);
        }
      }
      if (handler == null || handler instanceof UnknownTypeHandler) {
        // 最差的情况,设置为 ObjectTypeHandler
        handler = new ObjectTypeHandler();
      }
      // 将生成的 TypeHandler 存放在 typeHandlerMap 中
      columnHandlers.put(propertyType, handler);
    }
    return handler;
}

大致逻辑如下:

  1. 先从Map, TypeHandler>> typeHandlerMap属性中获取类型处理器
  2. 如果从缓存中没有获取到,则尝试根据Jdbc Type和Java Type从typeHandlerRegistry注册表获取
  3. 如果还是没有获取到,则根据classNames中拿到结果集中该列的Java Type,然后在从typeHandlerRegistry注册表获取
  4. 还是没有获取到,则设置为ObjectTypeHandler
  5. 最后将其放入typeHandlerMap缓存中
loadMappedAndUnmappedColumnNames方法

loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix)方法,初始化mappedColumnNamesMapunMappedColumnNamesMap两个属性,分别为映射的列名和未被映射的列名,方法如下:

private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
    List<String> mappedColumnNames = new ArrayList<>();
    List<String> unmappedColumnNames = new ArrayList<>();
    // <1> 获取配置的列名的前缀,全部大写
    final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
    /*
     * <2> 获取 ResultMap 中配置的所有列名,并添加前缀
     * 如果在 上面配置的是resultType属性,则返回的是空集合,因为它创建的ResultMap对象中只有Java Type属性

  • 遍历结果集中所有的列名,如果在标签中的子标签配置的column属性有包含这个列名,则属于映射的列名

  • 否则就属于未被映射的列名

  • ResultSetHandler

    org.apache.ibatis.executor.resultset.ResultSetHandler:结果集映射接口,代码如下:

    public interface ResultSetHandler {
      /**
       * 处理 {@link java.sql.ResultSet} 成映射的对应的结果
       *
       * @param stmt Statement 对象
       * @param   泛型
       * @return 结果数组
       * @throws SQLException SQL异常
       */
      <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    
      /**
       * 处理 {@link java.sql.ResultSet} 成 Cursor 对象
       *
       * @param stmt Statement 对象
       * @param   泛型
       * @return Cursor 对象
       * @throws SQLException SQL异常
       */
      <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
    
      /**
       * 暂时忽略,和存储过程相关
       *
       * @param cs CallableStatement 对象
       * @throws SQLException SQL异常
       */
      void handleOutputParameters(CallableStatement cs) throws SQLException;
    }
    

    DefaultResultSetHandler

    org.apache.ibatis.executor.resultset.DefaultResultSetHandler:实现ResultSetHandler接口,处理数据库的查询结果,对结果集进行映射,将结果转换成Java对象

    由于该类嵌套的方法太多了,可能一个方法会有十几层的嵌套,所以本分不会进行全面的分析

    因为我查看这个类的时候是从下面的方法一层一层往上看的,注释我全部添加了,所以可以参考我的注释一步一步查看

    接下来的描述可能有点混乱,请按照我在方法前面表明的顺序进行查看,参考:DefaultResultSetHandler.java

    先来看下DefaultResultSetHandler处理结果集的方法的流程图:

    MyBatis - SQL执行过程(十四)ResultSetHandler_第3张图片

    构造方法
    public class DefaultResultSetHandler implements ResultSetHandler {
        /**
         * 延迟加载默认对象
         */
        private static final Object DEFERRED = new Object();
        /**
         * 执行器
         */
        private final Executor executor;
        /**
         * 全局配置对象
         */
        private final Configuration configuration;
        /**
        * 本次查询操作对应的 MappedStatement 对象
        */
        private final MappedStatement mappedStatement;
        /**
         * 分页对象
         */
        private final RowBounds rowBounds;
        /**
         * 参数处理器,默认为 DefaultParameterHandler
         */
        private final ParameterHandler parameterHandler;
        /**
         * 结果处理器,默认为 DefaultResultHandler
         */
        private final ResultHandler<?> resultHandler;
        /**
         * SQL 相关信息
         */
        private final BoundSql boundSql;
        /**
         * 类型处理器注册表
         */
        private final TypeHandlerRegistry typeHandlerRegistry;
        /**
         * 对象实例工厂
         */
        private final ObjectFactory objectFactory;
        /**
         * Reflector 工厂
         */
        private final ReflectorFactory reflectorFactory;
    
    	// nested resultmaps
    	private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
    	private final Map<String, Object> ancestorObjects = new HashMap<>();
    	private Object previousRowValue;
    
    	// multiple resultsets
    	private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
    	private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
    
    	// Cached Automappings
    	private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
    
    	// temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
    	private boolean useConstructorMappings;
    
    	public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement,
    			ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
    		this.executor = executor;
    		this.configuration = mappedStatement.getConfiguration();
    		this.mappedStatement = mappedStatement;
    		this.rowBounds = rowBounds;
    		this.parameterHandler = parameterHandler;
    		this.boundSql = boundSql;
    		this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    		this.objectFactory = configuration.getObjectFactory();
    		this.reflectorFactory = configuration.getReflectorFactory();
    		this.resultHandler = resultHandler;
    	}
    }
    
    • 上面的属性有点多,可以先根据注释进行理解,也可以在接下来的方法中逐步理解
    1.handleResultSets方法

    handleResultSets(Statement stmt)方法,处理结果集的入口

    /**
     * 1.处理结果集
     */
    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        /*
         * <1> 用于保存映射结果集得到的结果队形
         * 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象,而实际上,每个 Object 是 List 对象
         */
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        // <2> 获取 ResultSet 对象,并封装成 ResultSetWrapper
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        /*
         * <3> 获得当前 MappedStatement 对象中的 ResultMap 集合,XML 映射文件中  标签生成的
         * 或者 配置 "resultType" 属性也会生成对应的 ResultMap 对象
         * 在  标签配置 ResultMap 属性时,可以以逗号分隔配置多个,如果返回多个 ResultSet 则会一一映射,通常配置一个

  • 如果有返回结果,但是没有 ResultMap 接收对象则抛出异常

  • 调用handleResultSet方法,完成结果集的映射,全部转换的 Java 对象,保存至 multipleResults 集合中,或者 this.resultHandler 中(用户自定的,通常不会)

  • 获取 resultSets 多结果集属性的配置,存储过程中使用,暂时忽略,本文暂不分析

  • 完成结果集映射的任务还是交给了2.handleResultSet方法

    2.handleResultSet方法

    handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping)方法,处理结果集

    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
                                 ResultMapping parentMapping) throws SQLException {
        try {
            if (parentMapping != null) {
                // <1> 暂时忽略,因为只有存储过程的情况时 parentMapping 为非空
                handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
            } else {
                if (resultHandler == null) { // <2>
                    // <2.1> 创建 DefaultResultHandler 默认结果处理器
                    DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
         			// <2.2> 处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中
                    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                    // <2.3> 将结果集合添加至 multipleResults 中
                    multipleResults.add(defaultResultHandler.getResultList());
                } else { // 用户自定义了 resultHandler,则结果都会保存在其中
          			// <3> 处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中
                    handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
                }
            }
        } finally {
            // issue #228 (close resultsets)
      		// <4> 关闭结果集
            closeResultSet(rsw.getResultSet());
        }
    }
    
    1. 暂时忽略,因为只有存储过程的情况时 parentMapping 为非空,查看上面的1.handleResultSets方法的第6
    2. 用户没有指定ResultHandler结果处理器
      1. 创建DefaultResultHandler默认结果处理器,就是使用一个List集合保存转换后的Java对象
      2. 调用handleRowValues方法,处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中
      3. 将结果集合添加至 multipleResults
    3. 用户指定了自定义的ResultHandler结果处理器,和第2步的区别在于,处理后的Java对象不会保存在multipleResults 中,仅保存在ResultHandler中,用户可通过它获取
    4. 关闭 ResultSet 结果集对象

    通常我们不会自定义结果处理器的,所以第4步本文暂不分析,我们来看到第2步,最终还是交给了3.handleRowValues方法

    3.handleRowValues方法

    handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)方法,处理结果集

    public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
            RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        /*
         * <1> ResultMap 存在内嵌的 ResultMap
         * 例如  标签中  或者  都会创建对应的 ResultMap 对象
         * 该对象的 id 会设置到 ResultMapping 的 nestedResultMapId 属性中,这就属于内嵌的 ResultMap
         */
        if (resultMap.hasNestedResultMaps()) { // 存在
          	// <1.1> 如果不允许在嵌套语句中使用分页,则对 rowBounds 进行校验,设置了 limit 或者 offset 则抛出异常,默认允许
            ensureNoRowBounds();
            // <1.2> 校验要不要使用自定义的 ResultHandler,针对内嵌的 ResultMap
            checkResultHandler();
      		// <1.3> 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping 的对应属性中
      		// 这里会处理内嵌的 ResultMap
            handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        } else {
          	// <2> 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping 的对应属性中
            handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        }
    }
    
    1. 如果当前 ResultMap 存在内嵌的 ResultMap

      例如 标签中 或者 都会创建对应的 ResultMap 对象,该对象的 id 会设置到 ResultMappingnestedResultMapId 属性中,这就属于内嵌的 ResultMap

      1. 如果不允许在嵌套语句中使用分页,则对 rowBounds 进行校验,设置了 limit 或者 offset 则抛出异常,默认允许
      2. 校验要不要使用自定义的 ResultHandler,针对内嵌的 ResultMap
      3. 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping(存储过程相关,本文暂不分析)的对应属性中,这里会对内嵌的 ResultMap 进行处理,调用handleRowValuesForNestedResultMap方法
    2. 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping(存储过程相关,本文暂不分析)的对应属性中,调用handleRowValuesForSimpleResultMap方法

    这里先来看到第2步中的4.handleRowValuesForSimpleResultMap方法,因为这个处理的情况相比第1步调用的方法简单些

    4.handleRowValuesForSimpleResultMap方法

    handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping 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();
        // <1> 根据 RowBounds 中的 offset 跳到到指定的记录
        skipRows(resultSet, rowBounds);
        // <2> 检测已经处理的行数是否已经达到上限(RowBounds.limit)以及 ResultSet 中是否还有可处理的记录
        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
          /*
           * <3> 获取最终的 ResultMap
           * 因为 ResultMap 可能使用到了  标签,需要根据不同的值映射不同的 ResultMap
           * 如果存在 Discriminator 鉴别器,则根据当前记录选择对应的 ResultMap,会一直嵌套处理
           */
          ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
          // <4> 从结果集中获取到返回结果对象,进行映射,比较复杂,关键方法!!!
          Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
          // <5> 将返回结果对象保存至 resultHandler,或者设置到父对象 parentMapping 的对应属性中
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
    }
    

    这里创建了一个DefaultResultContext保存结果的上下文对象,点击去你会发现有3个属性:

    • resultObject:暂存映射后的返回结果,因为结果集中可能有很多条数据
    • resultCount:记录经过 DefaultResultContext 暂存的对象个数
    • stopped:控制是否还进行映射
    1. 根据 RowBounds 中的 offset 跳到到结果集中指定的记录

    2. 检测已经处理的行数是否已经达到上限(RowBounds.limit)以及 ResultSet 中是否还有可处理的记录

    3. 调用resolveDiscriminatedResultMap方法,获取最终的 ResultMap

      因为 ResultMap 可能使用到了 标签,需要根据不同的值映射不同的 ResultMap
      如果存在 Discriminator 鉴别器,则根据当前记录选择对应的 ResultMap,会一直嵌套处理

    4. 调用getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,从结果集中获取到返回结果对象,进行映射,比较复杂,关键方法!!!

    5. 调用storeObject方法,将返回结果对象保存至 resultHandler,或者设置到父对象 parentMapping(存储过程相关,本文暂不分析)的对应属性中

    对于第345步的三个方法,我们一个一个来看

    • 4.1resolveDiscriminatedResultMap方法
    • 4.2getRowValue方法
    • 4.3storeObject方法
    4.1resolveDiscriminatedResultMap方法

    resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)方法,如果存在鉴别器,则进行处理,选择对应的 ResultMap,会一直嵌套处理

    public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
            throws SQLException {
        // 记录已经处理过的 ResultMap 的 id
        Set<String> pastDiscriminators = new HashSet<>();
        // <1> 获取 ResultMap 中的 Discriminator 鉴别器,标签会被解析成该对象
        Discriminator discriminator = resultMap.getDiscriminator();
        while (discriminator != null) {
            // <2> 获取当前记录中该列的值,通过类型处理器转换成了对应的类型
            final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
            // <3> 鉴别器根据该值获取到对应的 ResultMap 的 id
            final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
            if (configuration.hasResultMap(discriminatedMapId)) {
                // <3.1> 获取到对应的 ResultMap
                resultMap = configuration.getResultMap(discriminatedMapId);
                // <3.2> 记录上一次的鉴别器
                Discriminator lastDiscriminator = discriminator;
                // <3.3> 获取到对应 ResultMap 内的鉴别器,可能鉴别器里面还有鉴别器
                discriminator = resultMap.getDiscriminator();
                // <3.4> 检测是否出现循环嵌套了
                if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
                    break;
                }
            } else {
              	// <4> 鉴别结果没有对应的 ResultMap,则直接跳过
                break;
            }
        }
        // <5> 返回最终使用的 ResultMap 对象
        return resultMap;
    }
    
    1. 获取 ResultMap 中的 Discriminator 鉴别器, 标签会被解析成该对象

    2. 调用getDiscriminatorValue方法,获取当前记录中该列的值,通过类型处理器转换成了对应的类型,方法如下:

      private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
          // 获取 标签对应的的 ResultMapping 对象
          final ResultMapping resultMapping = discriminator.getResultMapping();
          // 获取 TypeHandler 类型处理器
          final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
          // 通过 TypeHandler 从 ResultSet 中获取该列的值
          return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
      }
      
    3. Discriminator 鉴别器根据该值获取到对应的 ResultMap 的 id

      1. 存在对应的 ResultMap 对象,则获取到
      2. 记录上一次的鉴别器
      3. 获取到对应 ResultMap 内的鉴别器,可能鉴别器里面还有鉴别器
      4. 检测是否出现循环嵌套了
    4. Discriminator 鉴别结果没有对应的 ResultMap,则直接跳过

    5. 返回最终使用的 ResultMap 对象

    4.2getRowValue方法

    getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,处理结果集

    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        // <1> 保存延迟加载的集合
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        // <2> 创建返回结果的实例对象(如果存在嵌套子查询且是延迟加载则为其创建代理对象,后续的延迟加载保存至 lazyLoader 中即可)
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    
        /*
         * <3> 如果上面创建的返回结果的实例对象不为 null,并且没有对应的 TypeHandler 类型处理器,则需要对它进行赋值
         * 例如我们返回结果为 java.lang.String 就不用了,因为上面已经处理且赋值了
         */
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // <3.1> 将返回结果的实例对象封装成 MetaObject,便于操作
            final MetaObject metaObject = configuration.newMetaObject(rowValue);
      		// <3.2> 标记是否成功映射了任意一个属性,useConstructorMappings 表示是否在构造方法中使用了参数映射
            boolean foundValues = this.useConstructorMappings;
            // <3.3> 检测是否需要自动映射
            if (shouldApplyAutomaticMappings(resultMap, false)) {
                /*
                 * <3.4> 从结果集中将未被映射的列值设置到返回结果 metaObject 中
                 * 返回是否映射成功,设置了1个或以上的属性值
                 */
                foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
            }
            /*
             * <3.5> 从结果集中将 ResultMap 中需要映射的列值设置到返回结果 metaObject 中
             * 返回是否映射成功,设置了1个或以上的属性值
             */
            foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
            foundValues = lazyLoader.size() > 0 || foundValues;
            /*
             * <3.6> 如果没有成功映射任意一个属性,则根据 returnInstanceForEmptyRow 全局配置(默认为false)返回空对象还是 null
             */
            rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        // <4> 返回该结果对象
        return rowValue;
    }
    
    1. 创建一个保存延迟加载的集合ResultLoaderMap对象lazyLoader,如果存在代理对象,创建的代理对象则需要通过它来执行需要延迟加载的方法,在后续会将到

    2. 调用createResultObject方法,创建返回结果的实例对象rowValue(如果存在嵌套子查询且是延迟加载则为其创建代理对象,后续的延迟加载保存至 lazyLoader 中即可)

    3. 如果上面创建的返回结果的实例对象rowValue不为 null,并且没有对应的 TypeHandler 类型处理器,则需要对它进行赋值

      例如我们返回结果为 java.lang.String 就不用了,因为上面已经处理且赋值了

      1. 将返回结果的实例对象封装成 MetaObject 对象metaObject,便于操作
      2. 标记是否成功映射了任意一个属性,useConstructorMappings 表示是否在构造方法中使用了参数映射
      3. 调用shouldApplyAutomaticMappings方法,检测是否需要自动映射,就是对未被映射的列进行处理
      4. 调用applyAutomaticMappings方法,从结果集中将未被映射的列值设置到返回结果 metaObject 中,返回是否映射成功(设置了1个或以上的属性值)
      5. 调用applyPropertyMappings方法,从结果集中将 ResultMap 中需要映射的列值设置到返回结果 metaObject 中,返回是否映射成功(设置了1个或以上的属性值)
      6. 如果没有成功映射任意一个属性,则根据 returnInstanceForEmptyRow 全局配置(默认为false)返回空对象还是 null
    4. 返回该结果对象rowValue

    我们逐步来看上面的第23.33.43.5所调用的方法

    • 4.2.1createResultObject方法
    • 4.2.2shouldApplyAutomaticMappings方法
    • 4.2.3applyAutomaticMappings方法
    • 4.2.4applyPropertyMappings方法
    4.2.1createResultObject方法

    createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix)方法,创建返回结果的实例对象(如果存在嵌套子查询且是延迟加载则为其创建代理对象)

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
            String columnPrefix) throws SQLException {
      	// 标记构造方法中是否使用了参数映射
        this.useConstructorMappings = false; // reset previous mapping result
        // <1> 记录构造方法的入参类型
        final List<Class<?>> constructorArgTypes = new ArrayList<>();
        // <2> 记录构造方法的参数值
        final List<Object> constructorArgs = new ArrayList<>();
        // <3> 创建返回结果的实例对象,该步骤的核心!!!
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        /*
         * <4> 如果返回结果的实例对象不为空,且返回结果没有对应的 TypeHandler 类型处理器
         * 则遍历所有的映射列,如果存在嵌套子查询并且要求延迟加载,那么为该返回结果的实例对象创建一个动态代理对象(Javassist)
         * 这样一来可以后续将需要延迟加载的属性放入 `lazyLoader` 中即可
         *
         * 为该对象创建对应的代理对象,其中通过 ResultLoaderMap 对延迟加载的方法进行了增强
         * 调用 getter 方法时执行查询并从 ResultLoaderMap 中删除,直接调用 setter 方法也会从中删除
         */
        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;
                }
            }
        }
        // <5> 记录是否使用有参构造方法创建的该返回结果实例对象
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
    }
    
    1. 记录构造方法的入参类型

    2. 记录构造方法的参数值

    3. 调用createResultObject方法(重载),创建返回结果的实例对象,该步骤的核心!!!

    4. 如果返回结果的实例对象不为空,且返回结果没有对应的 TypeHandler 类型处理器,例如一个实体类,则遍历所有的映射列,如果存在嵌套子查询并且要求延迟加载,那么为该返回结果的实例对象创建一个动态代理对象Javassist

      这样一来可以后续将需要延迟加载的属性放入 lazyLoader 中即可,在后续会讲到

    5. 记录是否使用有参构造方法创建的该返回结果实例对象,就是使用了映射,后续判断返回空对象还是null需要用到

    6. 返回实例对象,也可能是它的动态代理对象

    这里我们需要来看到第3步调用的createResultObject重载方法

    • 4.2.1.1createResultObject重载方法
    4.2.1.1createResultObject重载方法

    createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List constructorArgs, String columnPrefix)方法,找到构造方法,创建一个实例对象

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
            List<Object> constructorArgs, String columnPrefix) throws SQLException {
        // 获取 Java Type
        final Class<?> resultType = resultMap.getType();
        // 创建对应的 MetaClass 对象,便于操作
        final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
        // 获取  标签下构造函数的入参信息,可以通过这些入参确认一个构造函数
        final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    
        /*
         * 创建结果对象,分为下面4种场景:
         * 1. 结果集只有一列,且存在对应的 TypeHandler 类型处理器,例如返回 java.lang.String
         * 2.  标签下配置的  标签下的构造函数参数信息不为空
         * 3. 返回类型为接口,或者有默认的构造方法
         * 4. 找到合适的构造方法
         */
        if (hasTypeHandlerForResultObject(rsw, resultType)) { // 场景1
            // 将该列转换成对应 Java Type 的值,然后返回
            return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        } else if (!constructorMappings.isEmpty()) { // 场景2
            // 根据  标签下的构造方法入参配置,尝试从结果集中获取入参值,并创建返回结果的实例对象
            return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
        } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { // 场景3
          	// 使用默认无参构造方法创建返回结果的实例对象
            return objectFactory.create(resultType);
        } else if (shouldApplyAutomaticMappings(resultMap, false)) { // 场景4
            // 找到合适的构造方法并创建返回结果对象
            return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
        }
        throw new ExecutorException("Do not know how to create an instance of " + resultType);
    }
    

    创建结果对象,依次分为下面4种场景:

    1. 结果集只有一列,且存在对应的 TypeHandler 类型处理器,例如返回 java.lang.String

      则调用createPrimitiveResultObject方法,将该列转换成对应 Java Type 的值,然后返回

    2. 标签下配置的 标签下的构造函数参数信息不为空

      则调用createParameterizedResultObject方法,根据 标签下的构造方法入参配置,尝试从结果集中获取入参值,并创建返回结果的实例对象

    3. 返回类型为接口,或者有默认的构造方法

      则通过实例工厂objectFactory,使用默认无参构造方法创建返回结果的实例对象

    4. 找到合适的构造方法

      则调用createByConstructorSignature方法,找到合适的构造方法并创建返回结果对象

    好的,接下来我们又要看到第124步调用的三个方法了

    • 4.2.1.2createPrimitiveResultObject方法
    • 4.2.1.3createParameterizedResultObject方法
    • 4.2.1.4createByConstructorSignature方法
    4.2.1.2createPrimitiveResultObject方法

    createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,创建返回结果实例对象(通常是Java定义的类型,例如java.lang.String)

    private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
            throws SQLException {
        // 获取 Java Type
        final Class<?> resultType = resultMap.getType();
        final String columnName;
        /*
         * 获取列名
         */
        if (!resultMap.getResultMappings().isEmpty()) { // 配置了 
          // 获取  标签下的配置信息
            final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
            // 因为只有一个参数,则直接取第一个
            final ResultMapping mapping = resultMappingList.get(0);
            // 从配置中获取 column 属性
            columnName = prependPrefix(mapping.getColumn(), columnPrefix);
        } else {
          // 从结果集中获取列名
            columnName = rsw.getColumnNames().get(0);
        }
        // 通过 Java Type 和列名获取对应的 TypeHandler
        final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
        // 通过 TypeHandler 将返回结果转换成对应 Java Type 的值
        return typeHandler.getResult(rsw.getResultSet(), columnName);
    }
    
    1. 通过ResultSetWrapper根据Java Type和columnName找到对应的TypeHandler类型处理器
    2. 通过TypeHandler类型处理器,将结果集中的结果转换成对应的 Java 对象
    4.2.1.3createParameterizedResultObject方法

    createParameterizedResultObject(ResultSetWrapper rsw, Class resultType, List constructorMappings, List> constructorArgTypes, List constructorArgs, String columnPrefix)方法

    根据 标签下的 标签配置的参数构建一个实例对象

    Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
            List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
            String columnPrefix) {
        // 标记是否找到配置的构造函数的所有入参
        boolean foundValues = false;
        for (ResultMapping constructorMapping : constructorMappings) {
            // 获取参数的 Java Type
            final Class<?> parameterType = constructorMapping.getJavaType();
            // 获取参数对应的 column 列名
            final String column = constructorMapping.getColumn();
            final Object value;
            try {
                /*
                 * 获取该属性值,可能存在以下几种场景:
                 * 1. 存在嵌套查询
                 * 2. 存在嵌套 ResultMap
                 * 3. 直接获取值
                 */
                if (constructorMapping.getNestedQueryId() != null) { // 场景1
                  	// 通过嵌套查询获取到该属性值
                    value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
                } else if (constructorMapping.getNestedResultMapId() != null) { // 场景2
                  	// 获取到嵌套的 ResultMap 对象
                    final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
                    // 从结果集中获取到嵌套 ResultMap 对应的值
                    value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
                } else { // 场景3
                    final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
                    // 通过 TypeHandler 从结果集中获取该列的值
                    value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
                }
            } catch (ResultMapException | SQLException e) {
                throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
            }
            constructorArgTypes.add(parameterType);
            constructorArgs.add(value);
            foundValues = value != null || foundValues;
        }
        // 如果构造函数的入参全部找到,则创建返回结果的实例对象
        return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
    }
    
    1. 需要先从结果集中获取每个 标签配置的参数对应的值,这里又可能存在以下三种情况:
      1. 该参数存在嵌套查询,则调用getNestedQueryConstructorValue方法,获取到该属性值
      2. 存在嵌套 ResultMap,则调用getRowValue方法,从该结果集中获取到嵌套 ResultMap 对应的值,回到了4.2getRowValue方法
      3. 正常情况,通过TypeHandler类型处理器,根据列名从结果集中获取到该属性值
    2. 通过objectFactory实例工厂,根据上面配置的入参信息构建一个实例对象

    这里我们又要进入第1.1步的方法

    • 4.2.1.3.1getNestedQueryConstructorValue方法
    4.2.1.3.1getNestedQueryConstructorValue方法

    getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)方法,处理构造方法的入参出现嵌套子查询这种情况,获取该参数值

    private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
            throws SQLException {
        // <1> 获得嵌套查询关联的 id
        final String nestedQueryId = constructorMapping.getNestedQueryId();
        // <2> 获取嵌套查询对应的 MappedStatement 对象
        final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
        // <3> 获取嵌套查询的参数类型
        final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
        // <4> 获取嵌套查询的参数对象,已完成初始化
        final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
        Object value = null;
        // <5> 执行查询
        if (nestedQueryParameterObject != null) {
            // <5.1> 获取嵌套查询中的 SQL 对象
            final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
            // <5.2> 获取CacheKey对象
            final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
            final Class<?> targetType = constructorMapping.getJavaType();
            // <5.3> 创建 ResultLoader 对象
            final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, 
                                                               nestedQueryParameterObject, targetType, key, nestedBoundSql);
            // <5.4> 加载结果
            value = resultLoader.loadResult();
        }
        return value;
    }
    
    1. 获得嵌套查询关联的 id
    2. 获取嵌套查询对应的 MappedStatement 对象
    3. 获取嵌套查询的参数类型
    4. 获取嵌套查询的参数对象,已完成初始化,调用prepareParameterForNestedQuery方法,进去后发现又得两层方法,这里就不再展开了,比较简单,可以先参考的我的注释查看,在后续还会调用该方法,再进行解析
    5. 执行查询,因为这里的构造方法中的入参,所以无需判断延迟加载,在后面设置属性时就不一样了
      1. 获取嵌套查询中的 SQL 对象
      2. 获取CacheKey对象
      3. 创建 ResultLoader 对象
      4. 加载结果
    6. 返回子查询返回的值
    4.2.1.4createByConstructorSignature方法

    createByConstructorSignature(ResultSetWrapper rsw, Class resultType, List> constructorArgTypes, List constructorArgs)方法,尝试找一个合适的构造方法构建一个实例对象

    private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
        // <1> 获取所有的构造函数
        final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
        // <2> 找到添加了 @AutomapConstructor 注解的构造方法
        final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
        if (defaultConstructor != null) {
            // 使用这个构造方法创建返回结果的实例对象
            return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
        } else {
            for (Constructor<?> constructor : constructors) { // <3> 遍历所有的构造方法
                // 如果构造方法的入参与结果集中列的个数相同,并且入参的 Java Type 和列的 Jdbc Type 有类型处理器
                if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
                    // 使用这个构造方法创建返回结果的实例对象
                    return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);
                }
            }
        }
        throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
    }
    
    1. 获取所有的构造函数
    2. 找到添加了 @AutomapConstructor 注解的构造方法,如果存在则调用createUsingConstructor方法,创建一个实例对象
    3. 否则,遍历所有的构造方法
      1. 如果构造方法的入参与结果集中列的个数相同,并且入参的 Java Type 和列的 Jdbc Type 有类型处理器
      2. 使用这个构造方法创建返回结果的实例对象,调用createUsingConstructor方法,创建一个实例对象

    上面需要调用的createUsingConstructor方法比较简单,这里就不再展开了,大致逻辑就是从结果集中获取到该构造方法所有的入参,然后构建一个实例对象

    4.2.2shouldApplyAutomaticMappings方法

    shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested)方法,检测是否需要自动映射(对未被映射的列进行处理)

    private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
        /*
         * 获取中的 autoMapping 配置
         * 如果不为空则返回该值,是否自定映射
         */
        if (resultMap.getAutoMapping() != null) {
            return resultMap.getAutoMapping();
        } else {
            /*
             * 全局配置 AutoMappingBehavior 默认为 PARTIAL
             * 如果是嵌套,这里默认就返回 false
             */
            if (isNested) { // 嵌套映射
                return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
            } else {
                return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
            }
        }
    }
    
    1. 如果中的autoMapping配置不为空,则返回该配置
    2. 否则通过全局配置来判断,默认PARTIAL,也就是不是嵌套映射则需要对未被映射的列进行处理,嵌套查询的话不会对未被映射的列进行处理(需要配置为FULL
    4.2.3applyAutomaticMappings方法

    applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)方法,对未被映射的字段进行映射

    private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
            String columnPrefix) throws SQLException {
    	// <1> 将这些未被映射的字段创建对应的 UnMappedColumnAutoMapping 对象
        List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    	// 标记是否找到1个以上的属性值,延迟加载也算
        boolean foundValues = false;
        if (!autoMapping.isEmpty()) {
          // <2> 遍历未被映射的字段数组,将这些属性设置到返回结果对象中
            for (UnMappedColumnAutoMapping mapping : autoMapping) {
                // <2.1> 通过 TypeHandler 获取未被映射的字段的值
                final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
                if (value != null) {
                    foundValues = true;
                }
                /*
                 * <2.2> 如果属性值不为空,或者配置了值为 null 也往返回结果设置该属性值(不能是基本类型)
                 */
                if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
          			// 往返回结果设置属性值
                    metaObject.setValue(mapping.property, value);
                }
            }
        }
        return foundValues;
    }
    
    1. 调用createAutomaticMappings方法,将这些未被映射的字段创建对应的 UnMappedColumnAutoMapping 对象(包含列名、属性名、类型处理器、是否为原始类型)
    2. 遍历未被映射的字段数组,将这些属性设置到返回结果对象中
      1. 通过 TypeHandler 类型处理器获取未被映射的字段的值
      2. 如果属性值不为空,或者配置了值为 null 也往返回结果设置该属性值(不能是基本类型),则往返回结果中设置该属性值

    这里我们来看到createAutomaticMappings方法

    • 4.2.3.1createAutomaticMappings方法
    4.2.3.1createAutomaticMappings方法

    createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)方法,将这些未被映射的字段创建对应的 UnMappedColumnAutoMapping 对象

    private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
            MetaObject metaObject, String columnPrefix) throws SQLException {
      	// <1> ResultMap 中需要 "自动映射" 的列会缓存起来,这是对应的缓存 key
        final String mapKey = resultMap.getId() + ":" + columnPrefix;
        // <2> 先从缓存中获取
        List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
        if (autoMapping == null) {
            autoMapping = new ArrayList<>();
            // <3> 获取未映射的的列名集合,也就是数据库返回的列名在 ResultMap 中没有配置,例如我们配置的是 resultType 属性就全部没有配置
            final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
            for (String columnName : unmappedColumnNames) {
                String propertyName = columnName;
                /*
                 * <4> 如果配置了前缀,则将列名中的前缀去掉作为属性名
                 */
                if (columnPrefix != null && !columnPrefix.isEmpty()) {
                    // When columnPrefix is specified, ignore columns without the prefix.
                    if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
                        // 如果列名以前缀开头则将前缀去除
                        propertyName = columnName.substring(columnPrefix.length());
                    } else {
                        continue;
                    }
                }
                /**
                 * <5> 根据列名从入参对象中获取对应的属性名称,不管大小写都可以找到
                 * {@link org.apache.ibatis.reflection.Reflector#caseInsensitivePropertyMap)
                 */
                final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
                // <6> 开始创建 UnMappedColumnAutoMapping 对象
                if (property != null && metaObject.hasSetter(property)) {
                    if (resultMap.getMappedProperties().contains(property)) {
            			// 如果该属性配置了映射关系则跳过
                        continue;
                    }
                    // <6.1> 获取属性名称的 Class 对象
                    final Class<?> propertyType = metaObject.getSetterType(property);
                    if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
                        final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
                        // <6.2.1> 创建该属性的 UnMappedColumnAutoMapping 对象,设置列名、属性名、类型处理器、是否为原始类型
                        autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
                    } else {
            			// <6.2.2> 执行发现自动映射目标为未知列(或未知属性类型)的行为,默认为 NONE,不做任何行为
                        configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType);
                    }
                } else {
                    // 执行发现自动映射目标为未知列(或未知属性类型)的行为,默认为 NONE,不做任何行为
                    configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
                            (property != null) ? property : propertyName, null);
                }
            }
            autoMappingsCache.put(mapKey, autoMapping);
        }
        return autoMapping;
    }
    
    1. ResultMap 中需要 “自动映射” 的列会缓存起来,这是对应的缓存 key
    2. 先从autoMappingsCache缓存中获取该 ResultMap 对应的 UnMappedColumnAutoMapping 集合 autoMapping,没有的话才进行接下来的解析
    3. 获取未映射的的列名集合,也就是数据库返回的列名在 ResultMap 中没有配置,例如我们配置的是 resultType 属性就全部没有配置,然后进行遍历
    4. 如果配置了前缀,则将列名中的前缀去掉作为属性名
    5. 根据列名从入参对象中获取对应的属性名称,不管大小写都可以找到
    6. 开始为该属性创建 UnMappedColumnAutoMapping 对象,如果返回对象中有该属性的 setter 方法
      1. 获取属性名称的 Class 对象
      2. 如果有对应的 TypeHandler 类型处理器,创建该属性的 UnMappedColumnAutoMapping 对象,设置列名、属性名、类型处理器、是否为原始类型,添加到autoMapping集合中
      3. 否则,执行发现自动映射目标为未知列(或未知属性类型)的行为,默认为 NONE,不做任何行为
    7. 该属性没有setter方法,执行发现自动映射目标为未知列(或未知属性类型)的行为,默认为 NONE,不做任何行为
    8. 返回autoMapping,并添加到autoMappingsCache缓存中
    4.2.4applyPropertyMappings方法

    applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)方法,将明确被映射的字段设置到返回结果中

    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
            ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        // <1> 获取 ResultMap 中明确需要进行映射的列名集合
        final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        // 标记是否找到1个以上的属性值,延迟加载也算
        boolean foundValues = false;
        // <2> 获取 ResultMap 中所有的 ResultMapping 对象
        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;
            }
            /*
             * <3> 从结果集获取属性值设置到返回结果中,处理以下三种场景:
             * 1. 配置的 column 属性为"{prop1:col1,prop2:col2}"这种形式,
             * 一般就是嵌套子查询,表示将col1和col2的列值设置到嵌套子查询的入参对象的prop1和prop2属性中
             * 2. 基本类型的属性映射
             * 3. 多结果集的场景处理,该属性来自另一个结果集
             *
             * 对于没有配置 column 属性不会处理
             */
            if (propertyMapping.isCompositeResult() // 场景1
                    || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) // 场景2
                    || propertyMapping.getResultSet() != null) { // 场景3
                // <4> 完成映射,从结果集中获取到对应的属性值
                Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
                // issue #541 make property optional
                final String property = propertyMapping.getProperty();
                if (property == null) {
                    // <4.1> 没有配置对应的 Java 属性则跳过
                  continue;
                } else if (value == DEFERRED) {
          			// <4.2> 如果是占位符,则跳过
                    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')
          			// <4.3> 将属性值设置到返回结果中
                    metaObject.setValue(property, value); // 设置属性值
                }
            }
        }
        return foundValues;
    }
    
    1. 获取 ResultMap 中明确需要进行映射的列名集合mappedColumnNames
    2. 获取 ResultMap 中所有的 ResultMapping 对象,然后进行遍历
    3. 从结果集获取属性值设置到返回结果中,需要满足下面三个条件中的一个:
      • 配置的 column 属性为{prop1:col1,prop2:col2}这种形式,一般就是嵌套子查询,表示将col1和col2的列值设置到嵌套子查询的入参对象的prop1和prop2属性中
      • 基本类型的属性映射
      • 多结果集的场景处理,该属性来自另一个结果集,存储过程相关,本文暂不分析
    4. 完成映射,调用getPropertyMappingValue方法,从结果集中获取到对应的属性值value
      1. 没有配置对应的 Java 属性则跳过
      2. 如果是DEFERRED占位符(延迟加载),则跳过
      3. 将属性值设置到返回结果中

    我们来看看getPropertyMappingValue方法

    • 4.2.4.1getPropertyMappingValue方法
    4.2.4.1getPropertyMappingValue方法

    getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)方法

    完成映射,从结果集中获取到对应的属性值

    private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
            ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        if (propertyMapping.getNestedQueryId() != null) { // 嵌套子查询
            // <1> 执行嵌套子查询,返回查询结果,如果需要延迟记载则返回的是 DEFERRED
            return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
        } else if (propertyMapping.getResultSet() != null) { // 多结果集,存储过程相关,暂时忽略
            // <2> 多结果集处理,延迟加载,返回占位符
            addPendingChildRelation(rs, metaResultObject, propertyMapping);
            return DEFERRED;
        } else { // 结果映射
            // 获取 TypeHandler 类型处理器
            final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
            final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
            // <3> 通过 TypeHandler 类型处理器从结果集中获取该列对应的属性值
            return typeHandler.getResult(rs, column);
        }
    }
    

    可以看到该ResultMapping属性配置可能有三种情况:

    1. 如果是嵌套子查询,则调用getNestedQueryMappingValue方法,执行嵌套子查询,返回查询结果,如果需要延迟记载则返回的是 DEFERRED
    2. 如果是多结果集,存储过程相关,则调用addPendingChildRelation方法, 多结果集处理,延迟加载,返回占位符,本文暂不分析
    3. 正常情况,获取 TypeHandler 类型处理器,通过它从结果集中获取该列对应的属性值

    我们这里继续看到第1步中的调用的方法

    • 4.2.4.2getNestedQueryMappingValue方法
    4.2.4.2getNestedQueryMappingValue方法

    getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)方法

    执行嵌套子查询,返回查询结果,如果需要延迟记载则返回的是 DEFERRED

    private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
            ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        // <1> 获取嵌套子查询关联的ID
        final String nestedQueryId = propertyMapping.getNestedQueryId();
        // 获得属性名
        final String property = propertyMapping.getProperty();
        // 获得嵌套子查询的 MappedStatement 对象
        final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
        // 获得嵌套子查询的参数类型
        final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
        // <2> 准备好嵌套子查询的入参
        final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
                nestedQueryParameterType, columnPrefix);
        Object value = null;
        if (nestedQueryParameterObject != null) {
            // <3> 获得嵌套子查询的 BoundSql 对象
            final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
            // <4> 获得嵌套子查询本次查询的 CacheKey 对象
            final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
      		// 嵌套子查询的返回 Java Type
            final Class<?> targetType = propertyMapping.getJavaType();
            // <5> 检查缓存中已存在
            if (executor.isCached(nestedQuery, key)) {
                // <5.1> 创建 DeferredLoad 对象,并通过该 DeferredLoad 对象从缓存中加载结果对象
                executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
                // <5.2> 返回已定义
                value = DEFERRED;
            } else { // <6> 缓存中不存在
                // <6.1> 创建 ResultLoader 对象
                final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
                        nestedQueryParameterObject, targetType, key, nestedBoundSql);
                if (propertyMapping.isLazy()) { // <6.2> 如果要求延迟加载,则延迟加载
                    // <6.2.1> 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象
                    lazyLoader.addLoader(property, metaResultObject, resultLoader);
                    // <6.2.2> 返回延迟加载占位符
                    value = DEFERRED;
                } else { // <6.3> 如果不要求延迟加载,则直接执行加载对应的值
                    value = resultLoader.loadResult();
                }
            }
        }
        return value;
    }
    
    1. 获取嵌套子查询关联的ID、属性名、嵌套子查询的 MappedStatement 对象、嵌套子查询的参数类型

    2. 从结果集中获取参数值,准备好嵌套子查询的入参,调用prepareParameterForNestedQuery方法

    3. 获得嵌套子查询的 BoundSql 对象

    4. 获得嵌套子查询本次查询的 CacheKey 对象、嵌套子查询的返回 Java Type

    5. 检查缓存中已存在本次子查询的数据,已存在的话则进行下面两步

      1. 则创建 DeferredLoad 对象,并通过该 DeferredLoad 对象从缓存中加载结果对象

        这也算延迟加载,嵌套子查询的结果在缓存中,然后会在查询接口后进行加载,可以回到《SQL执行过程(一)之Executor》BaseExecutor小节中的query方法的第6步看看

      2. 返回DEFERRED延迟加载默认对象

    6. 缓存中不存在本次子查询的数据

      1. 创建 ResultLoader 对象resultLoader

      2. 如何该属性还要求了是延迟加载

        1. 则将其添加到ResultLoader.loaderMap 中,等待真正使用时再执行嵌套查询并得到结果对象

          可以回到4.2.1createResultObject方法的第4步看一下,如果存在嵌套子查询并且要求延迟加载,那么为该返回结果的实例对象创建一个动态代理对象Javassist),后续将需要延迟加载的属性放入 lazyLoader (就是上面的ResultLoader)中即可

        2. 返回DEFERRED延迟加载默认对象

      3. 否在直接加载resultLoader对象,获取到该属性值

    7. 最后返回该属性值或者DEFERRED延迟加载默认对象

    这里我们再来看到第2步中调用的方法

    • 4.2.4.2.1prepareParameterForNestedQuery方法
    4.2.4.2.1prepareParameterForNestedQuery方法

    prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix)方法,为嵌套子查询准备好入参

    private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
            String columnPrefix) throws SQLException {
        if (resultMapping.isCompositeResult()) { // 嵌套子查询是否有多个属性映射
          	// 从结果集中获取多个属性值设置到入参对象中
            return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
        } else {
          	// 从结果集中直接获取嵌套查询的入参
            return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
        }
    }
    
    1. 嵌套子查询是有多个属性映射,则调用prepareCompositeKeyParameter方法,从结果集中获取多个属性值设置到入参对象中

      配置的column属性为{prop1:col1,prop2:col2}这种形式,表示将col1和col2的列值设置到嵌套子查询的入参对象的prop1和prop2属性中

    2. 只有一个属性映射,则调用prepareSimpleKeyParameter方法,从结果集中直接获取嵌套查询的入参

    来看看第12步调用的方法

    • 4.2.4.2.2prepareCompositeKeyParameter方法
    • 4.2.4.2.3prepareSimpleKeyParameter方法
    4.2.4.2.2prepareCompositeKeyParameter方法

    prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix)方法

    处理嵌套子查询有多个属性映射作为入参的场景,获取到多个属性值到子查询的入参中

    private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
            String columnPrefix) throws SQLException {
      	// 创建一个嵌套子查询的入参的实例对象
        final Object parameterObject = instantiateParameterObject(parameterType);
        final MetaObject metaObject = configuration.newMetaObject(parameterObject);
        // 标记是否找到一个或以上的属性值
        boolean foundValues = false;
        for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
          	// 获取嵌套子查询的入参该属性的 Java Type
            final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
            final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
            //通过 TypeHandler 根据该属性的 column 列名从该结果集中获取值
            final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
            // issue #353 & #560 do not execute nested query if key is null
            if (propValue != null) {
              	// 设置属性值到入参对象中
                metaObject.setValue(innerResultMapping.getProperty(), propValue);
                foundValues = true;
            }
        }
        return foundValues ? parameterObject : null;
    }
    
    1. 创建一个嵌套子查询的入参的实例对象parameterObject,调用instantiateParameterObject方法,很简单,点击去看一下就知道了
    2. 开始遍历ResultMapping中的List composites组合字段
    3. 获取嵌套子查询的入参该属性对应的 TypeHandler 处理器
    4. 通过 TypeHandler 根据该属性的 column 列名从该结果集中获取值
    5. 设置属性值到子查询的入参对象中
    6. 返回parameterObject子查询入参对象
    4.2.4.2.3prepareSimpleKeyParameter方法

    prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix)方法,从结果集中直接获取嵌套查询的入参

    private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
            String columnPrefix) throws SQLException {
        final TypeHandler<?> typeHandler;
        if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
            typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
        } else {
            typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
        }
        // 根据类型处理器从结果集中获取该列的值,作为嵌套查询的入参
        return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
    }
    
    1. 根据Java Type获取到 TypeHandler 类型处理器
    2. 根据TypeHandler从结果集中将该列对应的值转化成入参
    4.3storeObject方法

    storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs)方法

    将返回结果 对象保存至 resultHandler,或者设置到父对象 parentMapping(存储过程相关,本文暂不分析)的对应属性中

    private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
            Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
        if (parentMapping != null) {
            // 嵌套查询或嵌套映射,将返回结果设置到父对象的对应属性中
            linkToParents(rs, parentMapping, rowValue);
        } else {
            // 普通映射,将结果对象保存到 ResultHandler 中
            callResultHandler(resultHandler, resultContext, rowValue);
        }
    }
    
    1. 如果parentMapping不为空,则调用linkToParents方法,嵌套查询或嵌套映射,将返回结果设置到父对象的对应属性中,存储过程相关,本文暂不分析
    2. 调用callResultHandler方法,普通映射,将结果对象保存到 ResultHandler 中

    来看到第2步调用的方法

    4.3.1callResultHandler方法

    callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue)方法

    普通映射,将结果对象保存到 ResultHandler 中

    private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
        /*
         * 递增返回结果数量 resultCount
         * 保存返回结果 resultObject
         */
        resultContext.nextResultObject(rowValue);
        // 将返回结果保存至 ResultHandler 中
        ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
    }
    
    1. resultContext上下文对象对象中暂存返回结果rowValue,并递增返回结果的数量,可以回到4.handleRowValuesForSimpleResultMap方法看一下

    2. 通过resultHandlerresultContext上下文对象暂存的返回结果进行处理,在DefaultResultHandler中你可以看到

      public class DefaultResultHandler implements ResultHandler<Object> {
      
      	private final List<Object> list;
      
      	public DefaultResultHandler() {
      		list = new ArrayList<>();
      	}
      
      	@Override
      	public void handleResult(ResultContext<?> context) {
      		list.add(context.getResultObject());
      	}
      }
      

      就是往List集合中添加返回结果

    结束语

    回顾到3.handleRowValues方法中,上面已经对结果集(不含嵌套ResultMap)进行映射的整个过程进行了分析,在defaultResultHandlermultipleResults都可以获取到映射后的结果

    本来想继续分析结果集(含嵌套ResultMap)这种情况的,调用的是handleRowValuesForNestedResultMap方法,由于上面已经嵌套太多层方法了,就不再分析第二种更复杂的情况,本文本身就不好编排,再进行嵌套就无法阅读了,可以参考DefaultResultSetHandler.java根据注释,进行理解

    其中涉及到的DefaultResultContextDefaultResultHandler都比较简单,也不列出来了,自行根据注释查看一下

    总结

    本文分析了DefaultResultSetHandler是如何处理结果集,进行映射,转换成Java对象的,总的来说就是根据ResultMap对象,对于不同的场景进行处理分析,映射成我们需要的Java对象,需要考虑的情况太多,所以这个类比较复杂,这里也仅对结果集(不含嵌套ResultMap)进行映射的整个过程进行了分析,关于含嵌套ResultMap的结果集来说,可能更加稍微复杂一点,不过也相差无几,可以参考DefaultResultSetHandler.java

    到这里SQL执行过程算是结束了,但是其中还有一部分延迟加载的内容没有分析,本来准备在这篇文档中分析的,发现已经写太多内容了,所以放在下一篇文档中

    参考文章:《MyBatis 源码分析》

    --------------最后感谢大家的阅读,愿大家技术越来越流弊!--------------

    在这里插入图片描述

    --------------也希望大家给我点支持,谢谢各位大佬了!!!--------------

    你可能感兴趣的:(MyBatis系列,spring,java,数据库,mysql)