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进行循环赋值。

相关推荐
计算机学姐14 分钟前
基于Python的高校成绩分析管理系统
开发语言·vue.js·后端·python·mysql·pycharm·django
wclass-zhengge1 小时前
SpringCloud篇(服务拆分 / 远程调用 - 入门案例)
后端·spring·spring cloud
ZWZhangYu1 小时前
【MyBatis源码】深入分析TypeHandler原理和源码
数据库·oracle·mybatis
A_cot1 小时前
一篇Spring Boot 笔记
java·spring boot·笔记·后端·mysql·spring·maven
tryCbest2 小时前
java8之Stream流
java·后端
白总Server2 小时前
JVM 处理多线程并发执行
jvm·后端·spring cloud·微服务·ribbon·架构·数据库架构
@sinner2 小时前
【Spring Boot 入门五】Spring Boot中的测试 - 确保应用质量
spring boot·后端·log4j
江梦寻3 小时前
解决SLF4J: Class path contains multiple SLF4J bindings问题
java·开发语言·spring boot·后端·spring·intellij-idea·idea
LightOfNight3 小时前
Redis设计与实现第9章 -- 数据库 总结(键空间 过期策略 过期键的影响)
数据库·redis·后端·缓存·中间件·架构
每天写点bug3 小时前
golang 常用的占位符 %w, %v, %s
开发语言·后端·golang