【MyBatis源码学习】Sql执行


前言

上一篇【MyBatis源码学习】Sql解析中,我主要梳理了sql的源码解析过程,本章我通过同样的一个例子,来仔细瞧瞧sql是怎样执行下来的。主要分为三个步骤:

  • 动态代理生成mapper
  • sql执行
  • 结果集处理

还是通过同样的例子来开始本章的源码sql执行过程跟踪。

@Test
// 程序员喜欢的风格
public void quickStart() throws IOException {
    // 2.获取sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3.获取对应mapper
    TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    // 4.执行查询语句并返回单条数据
    TUser user = mapper.selectByPrimaryKey(2);
    System.out.println(user);
}

一、动态代理生成mapper

我们直接从这句开始跟下源码:

TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

断点调式开始

""

我们看到SqlSession实例是DefaultSqlSession,点进去看看getMapper:

""

进入的是“大管家”Configuration的getMapper.

""

而真正给出接口代理类的是MapperRegistry.

""

点进去看看mapperProxyFactory.newInstance(sqlSession)具体生成实例的代码:

// MapperProxyFactory
// 生成一个 MapperProxy对象
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

发现,运用的正是JDK动态代理。
梳理下该过程的时序图,如下:

""

二、sql执行

通过一中的过程,我们可以得到一个接口的mapperProxy动态代理对象了。sql的执行过程就是从MapperProxy的invoke方法开始的。

""
""

先去缓存中看看是否已经有了现成的MapperMethod,没有才回去new一个出来。
接下来,调用 MapperMethod 中的execute方法。

""

本例中,执行的是一个查询语句,进入case SELECT分支


case SELECT: // 如果是查询语句
        if (method.returnsVoid() && method.hasResultHandler()) { // 返回为void,且有结果处理器
          // 使用结果处理器执行查询
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) { // 多条结果查询
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) { // map结果查询
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) { // 游标类型结果查询
          result = executeForCursor(sqlSession, args);
        } else { // 单条结果查询
          // 将参数顺序与实参对应好
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;

我们可以看到,根据返回的结果类型,进入不同的处理逻辑,主要有

  • 返回为void
  • 返回多条结果
  • 返回map结果
  • 返回单条结果

返回值为空的情况下,直接返回 result = null。其余几种情况内部都调用了sqlSession 中的selectList 方法。这里我们以selectOne为例。
convertArgsToSqlCommandParam()前面我们在【Mybatis源码学习】参数解析做过说明,主要进行参数的顺序映射解析。这里不做赘述。直接看看DefaultSqlSession.selectOne方法。

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    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;
    }
  }

如果selectList查询返回1条,则直接返回;如果返回多条则抛出异常,否则直接返回null。
点进去看看selectList方法。

/**
   * 查询结果列表
   * @param <E> 返回的列表元素的类型
   * @param statement SQL语句
   * @param parameter 参数对象
   * @param rowBounds  翻页限制条件
   * @return 结果对象列表
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 获取查询语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 交由执行器进行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

这里说明下,mybatis默认是开启了二级缓存的,所以这里executor其实是CachingExecutor.我们可以在创建sqlSession中看看Configuration中创建执行器时的代码:

""

要关闭二级缓存,我们可以在mybatis-config.xml中显示配置,如下:

<!-- 二级缓存开关,默认为开启-->
<setting name="cacheEnabled" value="false"/>

关闭二级缓存,我们回到selectList方法中,继续跟下这段代码的执行过程:

executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)

executor这里为SimpleExecutor,其首先调用的是BaseExecutor的query方法。

""
/**
   * 查询数据库中的数据
   * @param ms 映射语句
   * @param parameter 参数对象
   * @param rowBounds 翻页限制条件
   * @param resultHandler 结果处理器
   * @param key 缓存的键
   * @param boundSql 查询语句
   * @param <E> 结果类型
   * @return 结果列表
   * @throws SQLException
   */
  @SuppressWarnings("unchecked")
  @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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) { // 新的查询栈且要求清除缓存
      // 新的查询栈,故清除本地缓存,即清除一级缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 尝试从本地缓存获取结果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 本地缓存没有结果,故需要查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      // 懒加载操作的处理
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

先从一级缓存中查询是否已有结果,没有则才去查询数据库。我们直接看查询数据库的部分,也就是方法queryFromDatabase().

""

进入doQuery()方法,发现这里是个模版方法,由于前面提到我们此处的执行器为SimpleExecutor,所以这个方法来到了SimpleExecutor的doQuery()方法.

""

如上,该方法主要有三步:

  • 新建一个StatementHandler
  • 获取Statement
  • StatementHandler.query(实际调用的是PreparedStatementHandler)获取查询结果。

我们看看prepareStatement方法。

 //SimpleExecutor
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
   Statement stmt;
//获取数据库连接
   Connection connection = getConnection(statementLog);
   //创建Statement
   stmt = handler.prepare(connection);
   //为Statement设置IN参数
   handler.parameterize(stmt);
   return stmt;
 }

经典的流程:获取数据库连接;创建Statement; 为Statement设置IN参数。再看第三步执行语句:

 //PreparedStatementHandler
 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
   PreparedStatement ps = (PreparedStatement) statement;
// 执行SQL
   ps.execute();
// 处理执行结果
   return resultSetHandler.<E> handleResultSets(ps);
 }

查询完毕,总结一下这个过程的时序图,一目了然:

""

三、结果集处理

结果集的映射,这部分我会在下一篇详细的跟进。Game over.


文章作者: Kezade
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kezade !
评论
  目录