Mybatis源码 - SqlSession.selectOne(selectList)执行流程解析 一二级缓存 参数设置 结果集封装

前言

最近在学习Mybatis源码,学习到了SqlSession,里边查询相关的方法,主要有selectOneselectList等,而且selectOne方法,其实也是调用了selectList方法,然后根据数据条数进行了处理。

所以,在这里想把整个SqlSession.selectOne方法的执行流程梳理一遍,过程中会涉及sql获取一二级缓存设置方式&查询优先级查询参数设置结果集封装等内容。梳理整个流程,一来方便自己复习,学习优秀代码。二来也希望能够帮助到大家,话不多说,下面开始!

正文

selectOne方法

在Mybatis的数据库操作执行流程中,都会走到SqlSession接口,而在Mybatis初始化过程中,返回的是DefaultSqlSession,先看一下这个类的源码:

类中有多个重载的selectOne方法,我们就先看一下两个参数的方法,即

java 复制代码
public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

方法入参说明:

  1. String statement:传入的是statementId,是一条sql的唯一标识。由xml文件中的 namespacesql标签的id属性的值 拼接而来,例如com.xx.mapper.UserMapper.getUserByIdcom.xx.mapper.UserMapper是namespace的值,getUserById为select标签的id属性的值。
  2. Object parameter:传入的是sql的参数,这里是Object类型,因为传入的对象类型不固定。可能为一个字符串,也可能是一个User对象等等。

可以看出,上面的方法中,实际是调用了本类中的selectList方法,同时将两个参数进行了传入。

获取到结果后,对List集合的元素个数进行了判断,如果为1,直接返回第一个元素。如果大于1,则抛出异常,否则,就直接返回null。所以实际的查询逻辑 还是在selectList中,下面看一下这个方法

selectList方法

selectList其实有多个重载的方法,如下

java 复制代码
public class DefaultSqlSession implements SqlSession {
  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
  }
}

最后都会调用到一个四个参数的私有方法,这里先对方法的入参做一下说明

  1. String statement:statementId,一条sql的唯一标识。
  2. Object parameter:sql执行具体参数。
  3. RowBounds rowBounds:分页参数对象,根据该对象可以去实现逻辑分页。
  4. ResultHandler handler:结果集处理器,对查询结果进行处理封装。
java 复制代码
public class DefaultSqlSession implements SqlSession {
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

在这个方法中,先通过调用全局配置对象configuration的getMappedStatement方法,获取了MappedStatement对象,传入的参数是statement,也就是当前正在做查询操作的具体sql。

然后,将查询的操作继续委派给了底层的执行器,这个执行器的类型,应该是CachingExecutor,因为在Mybatis去创建SqlSession对象的时候,会进行框架是否允许缓存的判断,如果允许,会创建CachingExecutor对象,也就是执行器的装饰类,应用了装饰器模式,来对执行器的功能进行了增强(可见 Mybatis源码 - sqlSessionFactory.openSession()方法解析 创建事务对象 装饰器模式创建执行器)。

委派的过程中,参数也直接进行了传递,有所不同的是,sql的参数在这里进行了一层封装。

CachingExecutor.query()

那接着来看一下CachingExecutor的query方法

java 复制代码
public class CachingExecutor implements Executor {
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}

该方法的执行主要由三个步骤:

  1. 先通过MappedStatement对象,获取了BoundSql对象,该对象就是对每一条sql的封装。
  2. 创建了CacheKey对象,这里其实就是Mybatis本地缓存时使用的key,Mybatis本地缓存实际是一个Map集合,key为CacheKey对象,value为查询结果。
  3. 调用了本类方法query,执行了查询逻辑,并返回结果。

下面来详细分析一下每一步的细节

getBoundSql方法

MappedStatement类中的getBoundSql方法源码如下

java 复制代码
  public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

首先调用sqlSource对象的getBoundSql方法,获取BoundSql对象。这里的sqlSource对象是什么呢?

SqlSource是一个接口,它的实例由createSqlSource方法创建,它有不同的实现类,里面封装着解析完毕的sql语句,包括了完成占位符替换,参数值保存,动态标签解析等工作,换言之SqlSource中保存的就是 jdbc能够直接识别的sql语句(?作为占位符),请求参数的值 等(具体可见 Mybatis源码 - createSqlSource()方法解析 占位符替换 动态sql标签解析 )。

通过SqlSource获取到BoundSql对象之后,对parameterMappings进行了一些处理,例如 当parameterMappings为空时,说明sql语句中没有#{},这里创建了一个新的BoundSql对象,用来返回。还有会对ResultMapId不为null时进行了处理,这里是对嵌套结果集映射进行了处理。

方法的最后,返回了BoundSql对象。

createCacheKey方法

createCacheKey方法,顾名思义,创建一个对象,用来当作Mybatis本地缓存中的key,方法源码如下:

java 复制代码
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

这里调用了成员变量delegate的createCacheKey方法,这里的delegate对象,实际上就是SimpleExecutor,当前的执行器对象CachingExecutor,是对SimpleExecutor的一个包装。

而SimpleExecutor中并没有createCacheKey方法,那么就来到它的父类BaseExecutor中,这里其实是父类BaseExecutor提供的一个createCacheKey方法,用来规范了CacheKey的创建标准。看一下源码:

java 复制代码
  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

第6行,创建了CacheKey对象,可以先在下面的标题中了解一下CacheKey类的结构。

可以看到,几乎将每个参数,包括运行环境的ID,都传入了CacheKey对象的update方法,这些操作都会更新CacheKey里面成员变量的值。

第14-30行,对parameterMappings进行了处理,获取到了参数的值,最后也调用了CacheKey对象的update方法,更新了CacheKey。

方法最后,将创建好的CacheKey对象进行了返回。

拓展 - CacheKey类的结构

CacheKey源码如下,省略了部分代码

java 复制代码
public class CacheKey implements Cloneable, Serializable {

  private static final int DEFAULT_MULTIPLIER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  private final int multiplier;
  private int hashcode;
  private long checksum;
  private int count;
  private List<Object> updateList;

  public CacheKey() {
    this.hashcode = DEFAULT_HASHCODE;
    this.multiplier = DEFAULT_MULTIPLIER;
    this.count = 0;
    this.updateList = new ArrayList<>();
  }

  public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;

    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
  }

  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (!(object instanceof CacheKey)) {
      return false;
    }

    final CacheKey cacheKey = (CacheKey) object;

    if (hashcode != cacheKey.hashcode) {
      return false;
    }
    if (checksum != cacheKey.checksum) {
      return false;
    }
    if (count != cacheKey.count) {
      return false;
    }

    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (!ArrayUtil.equals(thisObject, thatObject)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public int hashCode() {
    return hashcode;
  }
}

可以看到几个细节:

  1. 类中定义了一系列的成员变量,例如multiplier、hashcode、checksum、count等,这些在update方法中都会重新进行计算,update方法传入的参数为Object,意味着可以传入任何类型的参数,在每传入一个参数,都会将上面的成员变量进行重新计算,最后会将这个参数添加到updateList这个集合中。
  2. 因为CacheKey对象,将来会放到Map集合中作为key来使用,所以为了避免哈希冲突,这里也对equals方法和hashCode方法进行了重写,制定了自己的计算规则。

本类query方法

在上面获取完BoundSql和创建完CahceKey之后,调用了本类中的query方法,源码如下

java 复制代码
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

方法一开始,执行了Cache cache = ms.getCache();,这里是从MappedStatement获取cache,这里是先来判断 是否开启了二级缓存,因为MappedStatement是解析了映射配置文件而来的,所以这里如果想要开启二级缓存,需要在映射配置文件中,添加<cache/>标签。

如果配置了二级缓存,那么执行流程如下:

  1. 第6行:会先调用flushCacheIfRequired方法,这个方法会判断当前sql,是否配置了flushCache为true,如果是的话,将会清除二级缓存。也就是说:如果当前mapper中配置了<cache/>标签,开启了二级缓存。但是sql中又配置了flushCache为true,那么相当于是不走缓存的,还是去查询数据库。
  2. 第7行:会判断当前sql是否配置了useCache为true,并且resultHandler==null,满足的话才会走下面的查询二级缓存逻辑。
  3. 第10-14行:从二级缓存中如果查不到数据,会调用delegate.query方法,去查询数据库,这里的delegate实际上是BaseExecutor。查询出数据之后,返回结果之前,会将查询出的结果再放回二级缓存(此处只是存入一个Map集合,并未真正放入二级缓存)。

如果没有配置二级缓存,那么将直接执行18行的delegate.query方法,去查询数据库。

这里可以总结出:二级缓存的开启方式是在mapper.xml文件中配置标签,同时,查询过程中 二级缓存是优先于一级缓存和数据库的。

BaseExecutor.query()

下面看一下,查询一级缓存以及数据库的流程,在前面的内容可以看出,最后调用的是BaseExecutor这个父类里面的query方法,下面看一下源码:

java 复制代码
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //如果sql配置了flushCache,并且queryStack == 0,那么将清除一级缓存
    //queryStack在这里应该是一个计数用的控制属性
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      //清除一级缓存,即PerpetualCache类中的一个Map集合
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //从一级缓存中查询到了数据,处理结果输出参数(只有StatementType是CALLABLE时)
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //从一级缓存中查不到数据,调用queryFromDatabase去查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

第9行:判断了ms.isFlushCacheRequired(),即sql中是否配置了flushCache属性,并且queryStack == 0的话,将清除一级缓存。

第16-23行:查询了一级缓存,如果没有数据,调用queryFromDatabase方法查询数据库,如果一级缓存中有数据,且StatementType == CALLABLE时(存储过程),会做一些结果输出参数的处理。

queryFromDatabase方法

该方法就是去查询数据库,看一下源码:

java 复制代码
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

第3-9行:这里先在一级缓存中占了个位,然后调用了doQuery方法查询数据库,finally块中又删除了一级缓存。然后又将查询出来的结果,放到了一级缓存中,最后将list返回。

查询数据时 调用了doQuery来查询数据,下面看一下这个方法。

SimpleExecutor.doQuery()

上面调用的doQuery方法,实际是SimpleExecutor中的方法。看一下源码:

java 复制代码
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

第5行:通过MappedStatement对象获取到了全局配置对象。

第6行:通过全局配置对象的newStatementHandler方法,获取到StatementHandler对象,这个是语句处理器,语句处理器是一个接口,有多个子类,这里返回的是RoutingStatementHandler,是一个装饰器类。类图如下:

newStatementHandler方法

看一下newStatementHandler的源码:

java 复制代码
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

第2行:直接创建了RoutingStatementHandler对象

第3行:实现插件逻辑。

java 复制代码
public class RoutingStatementHandler implements StatementHandler {
  private final StatementHandler delegate;
  
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }
}

可以看出,RoutingStatementHandler就是一个装饰类,里边有一个成员变量delegate来封装StatementHandler实例的引用,构造器中通过判断statementType(在sql语句的标签中配置属性值),为成员变量赋值了不同的语句处理器。

prepareStatement方法

接下来通过该方法,创建了Statement对象,看一下这个方法的源码:

java 复制代码
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

先对方法的入参做一个说明:

  1. StatementHandler handler:这就是语句处理器,传入的是RoutingStatementHandler,是一个装饰器类。
  2. Log statementLog:传入的是ms.getStatementLog(),是MappedStatement中的statementLog属性。

第3行:获取了Connection对象,获取过程中,会根据是否开启debug,返回不同的对象。如果开启了debug,会返回一个代理对象,否则就是普通的Connection对象。

java 复制代码
  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

第4行:调用RoutingStatementHandlerprepare方法,获取了Statement对象,获取过程中也会根据不同的情况,返回不同的对象。

java 复制代码
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

继续调用instantiateStatement方法,获取Statement对象。

java 复制代码
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

handler.parameterize() 参数设置

接下来通过该方法进行参数的设置,调用到PreparedStatementHandler中后,会委派给ParameterHandler来进行参数的设置,这是一个接口,默认的实现类为DefaultParameterHandler,下面来看一下这个类里边的setParameters方法,该方法完成了参数的设置。

java 复制代码
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

第4-5行:首先会获取存放参数的集合,参数集合不为空,才去遍历处理。

第8-20行:进行一系列的判断处理,例如 判断参数不是出参后,才继续处理等等。

第21-25行:获取到类型处理器TypeHandler,获取到JdbcType,还进行了jdbcType的校验。

第27行:通过TypeHandlersetParameter方法,完成了参数的设置。这里参数是从1开始的,所以设置时下标为i+1

handler.query()方法

SimpleExecutor.doQuery()方法中的最后一行,执行查询操作。源码第8行 ↓

java 复制代码
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

最后会调用到PreparedStatementHandlerquery方法。

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

第4行:执行sql语句

第5行:通过ResultSetHandler对结果集进行封装处理

ResultSetHandler处理结果集

Mybatis是通过ResultSetHandlerhandleResultSets方法来对查询结果进行处理的。该方法传入一个Statement,返回List<Object>集合,看一下源码:

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);

    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++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    //如果存在多个结果集,那么直接取出第一个
    return collapseSingleResultList(multipleResults);
  }

第8行:调用了getFirstResultSet,获取到第一个结果集,这里将返回值封装成了一个包装类ResultSetWrapper,看一下这个类的构造器:

java 复制代码
  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }

可以看出,这里不只封装了查询结果集,还封装了一些元数据信息,例如字段名、jdbcType等等。

第10-12行,获取了mapper中配置的ResultMap的个数,并进行了一些校验。

第13-19行:开始对结果集进行封装,重点在于15行的handleResultSet方法,该方法完成了结果集的封装,将结果封装到了multipleResults中,下面看一下源码

handleResultSet() 封装结果集

java 复制代码
  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());
    }
  }

第7行:创建了DefaultResultHandler

第8行:调用了handleRowValues方法,完成结果集封装。

第9行:将defaultResultHandler中的结果集添加到multipleResults,也就是最终结果集中。

handleRowValues方法

java 复制代码
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

这里看一下简单结果映射的方法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();
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      //通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      //将查询结果封装到pojo中
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      //保存映射结果
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

第4行:获取到结果集数据。

第5行:根据分页信息,提取相应记录。

第6-10:处理赋值多条记录。

第12行:保存映射结果。

getRowValue方法

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

第3行:根据resultType,创建一个对应的返回对象。

第5行:调用全局配置对象的newMetaObject方法,根据传入的rowValue,创建一个包含元数据的MetaObject对象。

第7行:判断是否要进行自动映射。(如果配置了resultType,则进行自动映射)。如果是,则调用applyAutomaticMappings进行循环赋值。

相关推荐
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04123 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫