Mybatis源码分析七

我们已经知道了 Mapper 方法执行的前因,即:Mybatis源码分析六)。现在就让我们来看看其“后果”:结果集映射。

参数映射是通过 TypeHandler 实现的,那么同理,结果集映射也应该是通过 TypeHandler 实现。

我们还是按照之前的方式,使用 debug 在入口代码上打断点,步入源码。入口代码为:

List<Author> list = mapper.selectByName("Sylvia");
   <resultMap id="AuthorMap" type="Author">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex" />
        <result column="phone" property="phone" />
    </resultMap>

    <select id="selectByName" resultMap="AuthorMap" >
        select
          id, name, sex, phone
        from author
        where name = #{name}
    </select>

我们接着 Mybatis源码分析五 一文最后我们找到的 JDBC 代码位置 org.apache.ibatis.executor.statement.PreparedStatementHandler 的 query(…) 方法来看:

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

在执行完 PreparedStatement 的 execute() 后,自然就要进行结果的处理,我们现在就进入最后一行结果处理的代码看看:
org.apache.ibatis.executor.resultset.DefaultResultSetHandler:

  @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;
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    //忽略 resultSets 部分...
    return collapseSingleResultList(multipleResults);
  }

这个方法就是在获取结果集并处理成我们最终想要的 List 类型的结果。我们提取一下关键步骤:

第一步:获取 PreparedStatement 执行数据库操作后返回的 ResultSet。
ResultSetWrapper rsw = getFirstResultSet(stmt);(方法的第 4 行代码)
第二步:获取 ResultMap。
List resultMaps = mappedStatement.getResultMaps();(方法的第 5 行代码)
ResultMap resultMap = resultMaps.get(resultSetCount);(方法的第 9 行代码)
第三步:通过前两步获得的 ResultSet 和 ResultMap 处理获得最终的 List 类型的结果。
handleResultSet(rsw, resultMap, multipleResults, null);(方法的第 10 行代码)

获取 ResultSet

ResultSetWrapper rsw = getFirstResultSet(stmt);
步入这行代码,进入本类的 getFirstResultSet(…) 方法:

  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }

在上一篇关于获取语句+参数映射的分析我们知道,这个方法传入的参数 stmt 是通过动态代理方法生成的,那么上面这个方法中的第一行代码:ResultSet rs = stmt.getResultSet(); 自然就会进入动态代理类 PreparedStatementLogger 中的 invoke(…) 方法来获取 ResultSet:

  @Override
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }          
      if (EXECUTE_METHODS.contains(method.getName())) {
        //这里省略了几个 else if
      } else if ("getResultSet".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      }
    //这里还有其他代码,继续省略...
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

我们主要来看 if (“getResultSet”.equals(method.getName() 这个判断后的代码:如果现在执行的方法是 getResultSet(…),那么首先会获取一个 ResultSet 的实例,接着会通过 ResultSetLogger.newInstance() 获取的 ResultSet 覆盖掉这个 ResultSet。同获取 PreparedStatement 实例非常相似,这里采用同样的方式:动态代理来通过 ResultSetLogger 代理类获取 ResultSet 实例。此处便不再赘述。

分析到这里我们就拿到了新出炉的 ResultSet,并将它封装到了 ResultSetWrapper 类中以供接下来的使用。

获取 ResultMap

关于获取 ResultMap,它是直接从 MappedStatement 中拿到的,而这个 MappedStatement 实例的内容是在获取 SqlSessionFactory 时填充的,
ResultMap
可以看到 ResultMap 已经包含了每个属性的结果映射 Map、要映射的列和属性,用于接下来的结果映射。

获取 List

handleResultSet(rsw, resultMap, multipleResults, null);

通过前两步,我们已经拿到了 ResultSet 和 ResultMap 了,现在我们就要来通过它们处理得到最终我们想要的 List 了。

首先要说明的一点是,这行代码是在 while 循环中,即通过遍历我们拿到的 ResultSetWrapper (封装了 ResultSet),来循环处理每一条结果集。现在就让我们步入这行代码,进入本类的 handleResultSet(…) 方法:

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

对于上面这个方法,其中第 6 、7 行代码为我们要跟踪的核心功能。其中,第 6 行代码通过 ObjectFactory 生成 DefaultResultHandler,该 DefaultResultHandler 持有生成的 List 实例。第 7 行代码则是给第 6 行代码执行生成的 List 填充值。而第 8 行代码则是将分页处理过后的 List 加入 List 类型的 multipleResults 中返回。
现在我们就来步入第 6 行核心代码看看:

  public DefaultResultHandler(ObjectFactory objectFactory) {
    list = objectFactory.create(List.class);
  }

在 DefaultResultHandler 类的构造方法中,调用了 ObjectFactory 的 create(…) 方法。想必大家对 ObjectFactory 已经不再陌生,在前面文档篇关于 ObjectFactory 的学习中我们知道:MyBatis 在获取到结果集后会通过 ObjectFactory 来创建结果对象的实例。它也是在 MyBatis 读取 Configuration XML 的时候存入的。也就是说,如果我们没有在 mybatis-config.xml 中配置自定义的 objectFactory,它会使用默认的 DefaultObjectFactory,默认只是实例化一个目标类。

为了印证这一点,我们跟踪 create(…) 方法来看一下 DefaultObjectFactory 中的实现:

  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Class<?> classToCreate = resolveInterface(type);
    // we know types are assignable
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
  }

上面这个方法第 1 行代码得到了 ArrayList 类型,继续步入:

  private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        return constructor.newInstance();
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) {
      StringBuilder argTypes = new StringBuilder();
      if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
        for (Class<?> argType : constructorArgTypes) {
          argTypes.append(argType.getSimpleName());
          argTypes.append(",");
        }
        argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
      }
      StringBuilder argValues = new StringBuilder();
      if (constructorArgs != null && !constructorArgs.isEmpty()) {
        for (Object argValue : constructorArgs) {
          argValues.append(String.valueOf(argValue));
          argValues.append(",");
        }
        argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
      }
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }

显而易见,原来 MyBatis 使用 ObjectFactory 生成目标类的实例,是通过反射实现的!对于反射,我想大家已经很熟悉了,这里就不展开分析了。

现在我们拿到了 ArrayList 实例,接下来就剩下往里面塞值了。我们接着看前面提到的第 7 行代码:handleResultSet(…) 方法的 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);。

进入 org.apache.ibatis.executor.resultset.DefaultResultSetHandler 类的 handleRowValuesForSimpleResultMap(…) 方法

  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()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }

我们重点来看 while 循环中的代码,该 while 循环遍历 ResultSet。

循环中的第 1 行代码的作用是对 ResultMap 进行进一步的处理:判断你是否定义了 Discriminator,如果定义了,它会去取你在 Discriminator 中指定的 ResultMap。
循环中的第 2 行代码的作用是获取 Bean 对象的实例并为该 Bean 对象赋值。
循环中的第 3 行代码的作用很简单,就是将获取到的 Bean 对象加入到 List 集合中。

我们想要知道 MyBatis 是如何将数据库获取到的每一条记录变成我们想要的集合中的 Java 对象的,所以第 2 行代码是我们要关注的重点。

步入这行代码,进入 getRowValue() 方法:

  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

我们来分析一下这个方法的主要功能:

第一步:获取 Bean 对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);(方法中第 2 行)
第二步:为 Bean 对象赋值,填充字段
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;(方法中第 9 行)
现在我们就来依次看一下具体是如何实现每一步的。

首先我们跟入第一步的代码,直到进入 org.apache.ibatis.executor.resultset.DefaultResultSetHandler 类的 createResultObject() 方法:

  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();
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
      return objectFactory.create(resultType);//对!就是这一行!
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
      return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
  }

这个方法首先要通过 ResultMap 获取要创建对象的类型,然后根据该类型判断要如何实例化。因为在该测试中,我们要获取一个普通 Bean 对象 Author 的实例,它有默认的构造方法,所以会进入方法的第 9 行代码:return objectFactory.create(resultType); 这行代码我想大家已经不陌生了,在获取 ArrayList 对象的时候也是通过 ObjectFactory 的 create() 实现的。那么也就是说,对于结果集 List 的 Bean 对象的实例化是通过 ObjectFactory 实现的,也就是通过反射实现的!
通过反射生成的 Author 对象
现在我们拿到了 Bean 对象,剩下的就是为这个 Bean 对象的每个字段根据我们查询的结果赋值了。我们进入 applyPropertyMappings 方法看一下:

  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      //略...
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        //略...
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

上面这个方法首先通过 ResultMap 获取到要映射的列名列表 mappedColumnNames 和属性结果映射列表
propertyMappings。然后遍历 propertyMappings,从 ResultSet 中获取每个列对应的值并将属性值存储到我们之前获得的 Author 对象中。其中,方法的第 10 行:Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); 为获取值,我们跟踪一下:

  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
      //略...
    } else {
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
  }

该方法首先获取该映射的 typeHandler 和列名,用 typeHandler 来获取:

  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getInt(columnName);
  }

这是我们熟悉的 JDBC 代码,但是需要提醒一下,我们前面分析过,这里的 ResultSet 是通过动态代理生成的,所以调用其方法不是直接从我们熟知的 ResultSet 获取,而是通过代理类 ResultSetLogger 来实现,那么接着它会进入到 ResultSetLogger 的 invoke():

  @Override
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }    
      Object o = method.invoke(rs, params);
      if ("next".equals(method.getName())) {
        if (((Boolean) o)) {
          rows++;
          if (isTraceEnabled()) {
            ResultSetMetaData rsmd = rs.getMetaData();
            final int columnCount = rsmd.getColumnCount();
            if (first) {
              first = false;
              printColumnHeaders(rsmd, columnCount);
            }
            printColumnValues(columnCount);
          }
        } else {
          debug("     Total: " + rows, false);
        }
      }
      clearColumnInfo();
      return o;
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

在该方法的第 5 行代码我们就得到了最终的值。好,现在我们拿到了字段的值,就差把值塞进 Author 里了。我们回到 DefaultResultSetHandler 的 applyPropertyMappings(…) 方法中,跟入 metaObject.setValue(property, value); 这行代码,直到进入 BeanWrapper 类的 setBeanProperty(…):

  private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
      Invoker method = metaClass.getSetInvoker(prop.getName());
      Object[] params = {value};
      try {
        method.invoke(object, params);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (Throwable t) {
      throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
    }
  }

很明显,这里也用的是反射,try 中的第一行就是在获取要调用的方法。MyBatis 中用到了大量的反射,源码中专门有一个 reflection 模块,就是封装 Java 反射的。我们先跟进去看一下:

  public Invoker getSetInvoker(String propertyName) {
    Invoker method = setMethods.get(propertyName);
    if (method == null) {
      throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
    }
    return method;
  }

我们看方法的第 1 行代码:Invoker method = setMethods.get(propertyName);,传入的参数是属性名,setMethods 是个 HashMap<String, Invoker>,key 即为属性名。setMethods 预先存储了 Bean 中所有属性及其对应的包含 set 方法的 MethodInvoker。
setMethods
那么现在我们就可以判断出来,第 1 行代码通过属性名获取到对应的包含了该属性的 set 方法的 MethodInvoker。然后返回到 BeanWrapper 的 setBeanProperty(…) 方法中,我们继续跟入 try 中的代码:method.invoke(object, params);:

  @Override
  public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
    return method.invoke(target, args);
  }

其中,这时的 method 为 public void com.zhaoxueer.learn.entity.Author.setName(java.lang.String),target 为 Author 对象,args 即为属性对应的值。那么接下来它就会进入我们定义的 Bean,即 Author 中的 setName(…) 方法:

    public void setName(String name) {
        this.name = name;
    }

这样,循环完所有的属性结果映射后,我们就会获得一个完美的已经被赋了数据库查询结果的值的 Author 对象。当我们执行完所有的循环后,我们就会最终拿到我们想要的 List 的结果列表。至此,大功告成!

最后,简单总结一下 Mapper 方法执行。我们分了四篇分别来分析了 Mapper 方法是如何执行的,以及 Mapper 方法执行的前因(获取语句+参数映射)后果(结果映射)。其中,从获取 Connection、获取 PreparedStatement 到获取 ResultSet 都使用动态代理的方式来实现,分步打印日志的同时获取对象。对于参数和结果映射都使用了 TypeHandler,我们不需要为每个要映射的属性或字段显式配置 TypeHandler,MyBatis 会根据参数值和结果字段值实际的类型来自动推算 TypeHandler。对于结果映射,当我们从数据库获得 ResultSet 之后,通过使用 ObjectFactory 来反射来实例化对应的 Java 对象,并通过反射的方式将字段值存储到 Java 对象中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值