mybatis-ResultHandler解析

news/2024/5/19 1:38:10 标签: java, mybatis, jdbc

概述

结果值的处理相当于参数的处理会复杂一些,负责结果值转换的类是 ResultSetHandler

java">public interface ResultSetHandler {

  /**
   * 处理数据集并返回
   * @param stmt
   * @param <E>
   * @return
   * @throws SQLException
   */
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

这里将存储过程、游标相关的忽略,我们只看普通的处理就行。

相当于就一个方法handleResultSets,处理ResultSet转换结果值。

ResultSetHandler只有一个实现类,即DefaultResultSetHandler。

但在解析结果值的时候还依赖一些其他的类。

在这里插入图片描述

ResultSetWrapper:ResultSet包装器,丰富ResultSet方法,包含了ResultSet相关元数据

ResultContext:结果值上下文,存储值结果值、总数等。

ResultHandler:结果处理器。做处理完的类型转换的结果,进行后置处理。

ResultSetWrapper

java">public class ResultSetWrapper {

  /**
   * 数据集
   */
  private final ResultSet resultSet;
  private final TypeHandlerRegistry typeHandlerRegistry;
  /**
   * 字段名集合
   */
  private final List<String> columnNames = new ArrayList<>();
  /**
   * 字段对应的javaType类型名
   */
  private final List<String> classNames = new ArrayList<>();
  /**
   * jdbcType
   */
  private final List<JdbcType> jdbcTypes = new ArrayList<>();
  // 类型处理器
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
  /**
   * 针对当前ResultSet中,resultMap中映射的字段名
   * key: resultMapId
   * value:resultMap映射字段
   */
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  /**
   * 针对当前ResultSet中,resultMap中未映射的字段名
   * key: resultMapId
   * 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++) {
      // columnLabel代表as的值,columnName代表原名
      // 默认取columnLabel的值
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      // 每个字段对应的jdbc类型
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      // 每个字段对应的java类型
      classNames.add(metaData.getColumnClassName(i));
    }
  }
}

ResultSetWrapper包装了ResultSet,通过ResultSetMetaData(JDBC)获取列信息。结合ResultMap进行列相关方法的提供。

看下ResultSetWrapper提供的所有方法:

在这里插入图片描述

基本是针对自身属性提供的get方法,了解了各个属性的意义之后,方法的作用也就比较清楚了。

ResultContext

java">public interface ResultContext<T> {

  T getResultObject();

  int getResultCount();

  boolean isStopped();

  void stop();

}

结果的载体,实现类 DefaultResultContext

DefaultResultContext

java">public class DefaultResultContext<T> implements ResultContext<T> {

  /**
   * 结果值
   */
  private T resultObject;
  /**
   * 累计的结果数量
   */
  private int resultCount;
  /**
   * 是否停止
   */
  private boolean stopped;

  public DefaultResultContext() {
    resultObject = null;
    resultCount = 0;
    stopped = false;
  }

  @Override
  public T getResultObject() {
    return resultObject;
  }

  @Override
  public int getResultCount() {
    return resultCount;
  }

  @Override
  public boolean isStopped() {
    return stopped;
  }

  public void nextResultObject(T resultObject) {
    resultCount++;
    this.resultObject = resultObject;
  }

  @Override
  public void stop() {
    this.stopped = true;
  }

}

so easy。

ResultHandler

java">public interface ResultHandler<T> {

  void handleResult(ResultContext<? extends T> resultContext);

}

传入ResultContext,进行结果后置处理

DefaultResultHandler

java">public class DefaultResultHandler implements ResultHandler<Object> {

  private final List<Object> list;

  public DefaultResultHandler() {
    list = new ArrayList<>();
  }

  @SuppressWarnings("unchecked")
  public DefaultResultHandler(ObjectFactory objectFactory) {
    list = objectFactory.create(List.class);
  }

  @Override
  public void handleResult(ResultContext<?> context) {
    // 就仅当结果存储
    list.add(context.getResultObject());
  }

  public List<Object> getResultList() {
    return list;
  }

}

主要就是将结果存储到list中。

DefaultMapResultHandler

主要是MapKey注解的实现。这里科普下@MapKey的作用,就是将mapper的返回值转换成Map类型。@MapKey 就是指定返回map的key。

java">public class DefaultMapResultHandler<K, V> implements ResultHandler<V> {

  /**
   * 对应的map结果
   * key:mapKey对应的值
   * value:原结果
   */
  private final Map<K, V> mappedResults;
  private final String mapKey;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;

  @SuppressWarnings("unchecked")
  public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    this.mappedResults = objectFactory.create(Map.class);
    this.mapKey = mapKey;
  }

  @Override
  public void handleResult(ResultContext<? extends V> context) {
    final V value = context.getResultObject();
    // 创建MetaObject实例
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    // 取出key值
    final K key = (K) mo.getValue(mapKey);
    // 构造map值
    mappedResults.put(key, value);
  }

  public Map<K, V> getMappedResults() {
    return mappedResults;
  }

ResultSetHandler

主人公出现,铺垫了相关类之后,开始分析ResultSetHandler。

java">public interface ResultSetHandler {

  /**
   * 处理数据集并返回
   * @param stmt
   * @param <E>
   * @return
   * @throws SQLException
   */
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  // 游标忽略
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  // 存储过程忽略
  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

ResultSetHandler就只有一个实现类 DefaultResultSetHandler

DefaultResultSetHandler

先看下DefaultResultSetHandler的属性和构造方法

java">public class DefaultResultSetHandler implements ResultSetHandler {

  private static final Object DEFERRED = new Object();

  // 执行器
  private final Executor executor;
  // 配置
  private final Configuration configuration;
  // 映射Statement
  private final MappedStatement mappedStatement;
  // 分页参数
  private final RowBounds rowBounds;
  // 参数处理器
  private final ParameterHandler parameterHandler;
  // 结果处理器
  private final ResultHandler<?> resultHandler;
  // 动态sql载体
  private final BoundSql boundSql;
  // 类型处理器容器
  private final TypeHandlerRegistry typeHandlerRegistry;
  // 对象工厂
  private final ObjectFactory objectFactory;
  // 反射器工厂
  private final ReflectorFactory reflectorFactory;
  // 嵌套结果
  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;
  }

这里实例化的入口是BaseStatementHandler的构造函数

java">protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  ...

  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

虽然参数比较多,但还算清楚。

DefaultResultSetHandler也就仅需要实现 handleResultSets这么一个方法。

我觉得分析源码比较好的方式是,先设想它的实现原理,然后再通过代码求证,这样提前有了自己的思考,在看源码时就可以挑重点的看,不至于被庞大的分支绕晕了。

那我们这里先想想结果值的处理会是怎么样的过程,

给到你一个ResultSet,怎么转换成目标类型?

  1. 遍历ResultSet行(一行一行处理,保存到List中一起返回)。
  2. 创建目标类型实例(构造函数反射)。
  3. 遍历ResultSet中的列,根据列的jdbc类型找到TypeHandler
  4. 通过TypeHandler和列的值转换成javaType。
  5. 将转换后的列值反射设置值到目标的属性中。

构思下大概流程,接着通过代码佐证。

handleResultSets

java">@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  final List<Object> multipleResults = new ArrayList<>();

  int resultSetCount = 0;
  /*
   * 获取第一个结果集,包装了一下
   * 基本上只有一个结果集,存储过程才可能有多个
   */
  ResultSetWrapper rsw = getFirstResultSet(stmt);

  /*
   *  获取结果映射map,这里虽然是list,但是我们日常几乎都是单ResultMap
   */
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);

  /*
   * 遍历resultMaps,从0开始,也就是按照resultMap中定义的字段顺序来处理
   *
   */
  while (rsw != null && resultMapCount > resultSetCount) {
    //每个数据集一个ResultMap
    ResultMap resultMap = resultMaps.get(resultSetCount);

    // 真正开始处理转换
    handleResultSet(rsw, resultMap, multipleResults, null);

    // 获取下一个数据集
    rsw = getNextResultSet(stmt);

    cleanUpAfterHandlingResultSet();

    //索引累加
    resultSetCount++;
  }
  
  // 存储过程相关代码忽略
  ...
 
  /*
   * 如果是单数据集,就将其展开返回
   * multipleResults的结构是List<List<Object>>
   * 这种结构是为了支持多数据集,存储过程可能返回多个数据集。
   */
  return collapseSingleResultList(multipleResults);
}

这里mybatis对存储过程和普通sql进行了通用的处理,体现在存储过程有多ResultSet、ResultMap,我们在分析的时候只关注与List的第一个元素即可。

handleResultSet

处理单个ResultSet。

java">private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    // 普通sql无父Mapping
    if (parentMapping != null) {
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      // 一般使用时 不会主动指定ResultHandler
      if (resultHandler == null) {
        /*
         * 处理数据,并将结果存储在ResultHandler上
         * 最终转嫁到multipleResults上,向上透出
         */
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        // 处理行值
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        // 将处理好的结果设置到multipleResults
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // issue #228 (close resultsets)
    closeResultSet(rsw.getResultSet());
  }
}

如果mapper方法中没有直接执行ResultHandler,那么创建默认的ResultHandler做结果收集。

handleRowValues

java">public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  //判断是否有嵌套ResultMap
  if (resultMap.hasNestedResultMaps()) {
    ensureNoRowBounds();
    checkResultHandler();
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
  // 普通ResultMap解析
  else {
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}

进入到简单ResultMap解析

handleRowValuesForSimpleResultMap

java">private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  ResultSet resultSet = rsw.getResultSet();

  /*
   * 根据分页参数的offset 跳过指定的行,ROwBounds是伪分页
   */
  skipRows(resultSet, rowBounds);

  /*
   * 遍历每一行数据
   */
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    //获取鉴别器ResultMap? 鉴别器使用很少,忽略
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);

    //获取一行的记录
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);

    //将结果(rowValue)存储在resultHandler上
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

这里 getRowValue 返回解析好的单行值,已经将单行转换成javaBean的实例了。

storeObject方法针对返回的行值进行ResultHandler处理。(默认ResultHandler就是存储起来)

继续跟进行值解析

getRowValue

java">private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();

  // 创建javaBean对应的实例,就是ResultMap上对应的javaType类型
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

  // 如果结果无直接的类型处理器,则进行字段映射
  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, columnPrefix) || foundValues;
    }

    // 通过MetaObject,进行属性值反射设置
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

createResultObject方法,创建对应javaBean实例,如果用户指定了其类型处理器,那么会交由其处理,如果未指定则需要进行属性映射。

这里createResultObject的实现就不跟了,我们清楚只是创建一个空属性的实例即可。

看下属性映射,MetaObject之前反射包解析过,可以像操作map一样操作普通类。

applyPropertyMappings

java">private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  //获取所有映射的字段名(即resultMap中主动写了映射的字段)
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  //获得所有属性映射
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();

  //遍历属性映射,挨个字段映射
  for (ResultMapping propertyMapping : propertyMappings) {
    String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    if (propertyMapping.getNestedResultMapId() != null) {
      // the user added a column attribute to a nested result map, ignore it
      column = null;
    }
    if (propertyMapping.isCompositeResult()
        || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
        || propertyMapping.getResultSet() != null) {

      /*
       * 属性值
       * 根据字段名从ResultSet中获取值,由typeHandler进行转换
       */
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      // issue #541 make property optional
      // 属性名
      final String property = propertyMapping.getProperty();
      if (property == null) {
        continue;
      } else if (value == DEFERRED) {
        foundValues = true;
        continue;
      }
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
        // gcode issue #377, call setter on nulls (value is not 'found')

        // 使用反射工具类,给属性赋值
        metaObject.setValue(property, value);
      }
    }
  }
  return foundValues;
}
  1. 遍历ResultMapping。
  2. 属性值获取
  3. 反射设置

中间一些嵌套查询、延迟加载的可以忽略。

getPropertyMappingValue

java">private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  // 嵌套,忽略
  if (propertyMapping.getNestedQueryId() != null) {
    return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  } 
  // 多数据集,忽略
  else if (propertyMapping.getResultSet() != null) {
    addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
    return DEFERRED;
  }
  // 正常情况
  else {
    final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
    final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    // 由类型处理器进行值获取、转换
    return typeHandler.getResult(rs, column);
  }
}

到这里结果值的处理已经结束,和之前设想的流程一对,几乎是一致的。

只是在分支上增加了对嵌套查询、存储过程等进行了处理。

总结

在对结果值的处理时,其实大体流程不复杂,但是mybatis考虑的点比较多,做了一个通用处理,我们抓住核心,就会发现和我们想的一样简单。


That’s All


http://www.niftyadmin.cn/n/1182352.html

相关文章

[报告]codeforces 175d Plane of Tanks: Duel

Abstract codeforces 175d Plane of Tanks: Duel dp 概率 乱搞 Source http://codeforces.com/problemset/problem/175/D Description 战车少女互殴 两辆战车对战&#xff0c;战车有如下属性&#xff1a;hp&#xff0c;射击时间间隔&#xff0c;击中目标概率&#xff0c;伤害值…

mybatis-sqlsession解析

概述 mybatis的执行流程&#xff08;来源于网络&#xff09;&#xff1a; 前几篇分析过Executor&#xff0c;执行器在StatementHandler上添加了缓存和事务的功能&#xff0c;但api还是比较偏底层&#xff0c;因此需要再Executor上再封装、增强一层&#xff0c;因此就有了SqlSe…

【函数】fill和fill_n填充之区别

fill对区间填充 原型: template < class ForwardIterator, class T > void fill ( ForwardIterator first, ForwardIterator last, const T& value ) {while (first ! last) *first value; } 填充区间[first,last) 示例&#xff1a; #include <iostream> #in…

ld-linux.so.2找不到

实际上我的并不是找不到&#xff0c;而是装了64位的&#xff0c;没有装32位的&#xff0c;是系统版本作怪经常会遇到linux下安装软件时提示少文件&#xff0c;如何知道所缺少的文件属于哪个包&#xff1f;用什么命令查看&#xff1f;例如:/lib/ld-linux.so.2: bad ELF interpre…

python 计时——time, datetime

时间戳 时间戳是自 1970 年 1 月 1 日&#xff08;08:00:00 GMT&#xff09;至当前时间的总秒数。它也被称为 Unix 时间戳&#xff08;Unix Timestamp&#xff09;&#xff0c;它在unix、c的世界里随处可见&#xff1b;常见形态是浮点数&#xff0c;小数点后面是毫秒。两个时间…

[C# 基础知识梳理系列]专题五:当点击按钮时触发Click事件背后发生的事情

引言&#xff1a; 当我们在点击窗口中的Button控件VS会帮我们自动生成一些代码&#xff0c;我们只需要在Click方法中写一些自己的代码就可以实现触发Click事件后我们Click方法中代码就会执行&#xff0c;然而我一直有一个疑问的——既然上一专题中说事件是一个多播委托&#xf…

怎么找SSDT第一个函数起始地址

打开windbg:Kernel Debug 选择Local: 输入&#xff1a;dd KeServiceDescriptorTable 依次输入&#xff1a; 这就获得了内核的函数所在起始地址。 原理呢&#xff1f; 转载于:https://www.cnblogs.com/forworldpeace/archive/2012/11/07/2758889.html

逃亡的准备--真正裸的多重背包

【问题描述】在《Harry Potter and the Deathly Hallows》中&#xff0c;Harry Potter他们一起逃亡&#xff0c;现在有许多的东西要放到赫敏的包里面&#xff0c;但是包的大小有限&#xff0c;所以我们只能够在里面放入非常重要的物品&#xff0c;现在给出该种物品的数量、体积…