MyBatis 源码解析(1): select 语句的执行过程

  • 时间:
  • 浏览:
  • 来源:互联网

MapperProxy 代理了 Mapper 接口

当使用 Mapper 接口执行 select 方法时, 实际是 Mapper 接口的代理类 MapperProxy 在执行.

MapperProxy 都干了些啥

最主要的工作就是创建了 MappeMethod, 并调用其 execut 方法来执行逻辑.

  1. 先看下创建 MappeMethod 的逻辑:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 主要是封装 SQL 命令
    this.command = new SqlCommand(config, mapperInterface, method);
    // 封装 Mapper 接口定义的方法的签名
    this.method = new MethodSignature(config, mapperInterface, method);
  }

逻辑非常清晰. SqlCommand 封装了 SQL 的 insert, delete, update, select, flush 命令, 以及我们在 Mapper 接口中定义的方法名. MethodSignature 封装了 Mapper 接口中方法签名.

  1. 再看看 execute 方法的逻辑(删除了部分源码):
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      // 1.判断 sql 命令类型
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          // 2.构造 SQL 参数
          Object param = method.convertArgsToSqlCommandParam(args);
          // 3.调用 SqlSession 的方法执行查询逻辑
          result = sqlSession.selectOne(command.getName(), param);
        }
    }
    return result;
}

逻辑也很清晰: 第一步, 判断 sql 命令的类型; 第二步, 构造SQL参数; 第三步, 调用 SqlSession(实际是 SqlSessionTemplate) 的 selectOne 方法执行逻辑.

SqlSessionTemplate

这个类是接口 SqlSession 的实现类. 上一步中调用 SqlSessionTemplate 的 selectOne 方法时, 实际上会被它的一个内部类 SqlSessionInterceptor 代理, 而这个代理类代理的就是 SqlSession 接口.

 public <T> T selectOne(String statement, Object parameter) {
    // sqlSessionProxy 就是 SqlSession 的代理类 SqlSessionInterceptor
    return this.sqlSessionProxy.selectOne(statement, parameter);
  }

SqlSessionInterceptor

代理类的代理方法执行的逻辑如下(只保留主要逻辑):

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1.获取 sqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
        //2.执行被代理的方法
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          sqlSession.commit(true);
        }
        return result;
    }

这里最重要的就是第一步, 获取 sqlSession 对象, 它才是具体执行的 SqlSession. 去看下获取 sqlSession 对象的逻辑.

getSqlSession

方法定义如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    // 获取缓存的 sqlSession
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    // 缓存不存在,则创建 sqlSession(DefaultSqlSession)
    session = sessionFactory.openSession(executorType);

    // 缓存刚刚创建的 sqlSession
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

这里需要注意的是, sqlSession 只会在开启了事务的情况下, 才会被绑定到当前线程缓存起来, 否则只会每次都创建一个新的 DefaultSqlSession 对象(具体缓存逻辑在TransactionSynchronizationManager#bindResource方法).

所以, getSqlSession 方法的逻辑很明确了: 获取 DefaultSqlSession 对象, 并调用其被代理的方法来执行逻辑.

至于为什么要用 SqlSessionInterceptor 来代理 SqlSession, 简单来说, 其原因是 DefaultSqlSession 是非线程安全的, SqlSessionTemplate 通过这个代理类实现了线程安全.这方面的具体内容, 我后面会专门安排一篇文章来详细展开.

DefaultSqlSession

最终会调用到下面这个方法(只保留主要逻辑):

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        // 1.获取 MapperedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 2.调用 CachingExecutor 的 query 方法
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      }

MapperedStatement 主要是维护了 Mapper.xml 文件的路径, 原始的 sql 语句和 sql 参数, 以及结果集的定义.

CachingExecutor

在 query 方法中主要做了两件事:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 1.构造 BoundSql, 封装了原始的 sql 语句和 sql 参数
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 2.创建 CacheKey, 用于缓存查询结果
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 3.继续调用重载的 query 方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

继续调用重载的 query 方法:

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) {
        // 判断缓存是否可用
      if (ms.isUseCache() && resultHandler == null) {
        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);
  }

主要逻辑是缓存可用, 则直接使用缓存. 否则调用delegate(SimpleExecutor,实际调用的是父类 BaseExecutor)的 query 方法.

BaseExecutor

query 方法的主要逻辑是缓存可用, 则直接使用缓存. 否则去数据库查:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    try {
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // 缓存可用
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 缓存不可用, 去查数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } 
    return list;
  }

queryFromDatabase 方法最终会被调用到 SimpleExecutor 的 doQuery 方法.

SimpleExecutor

doQuery 方法的主要逻辑是构建 Statement,并调用 RoutingStatementHandler 的 query 方法:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        // 构建 Statement.
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 调用 RoutingStatementHandler 的 query 方法.
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

Statement 封装的是已准备好的可执行的 sql 语句, 以及数据库连接的信息.

RoutingStatementHandler

query 方法又会被转发给 delegate(PreparedStatementHandler)的 query 方法:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 调用 PreparedStatementHandler 的 query 方法
    return delegate.query(statement, resultHandler);
  }

PreparedStatementHandler

query 方法定义如下:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 1.调用 ProxyPreparedStatement 的 execute 方法
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    // 2.返回处理结果
    return resultSetHandler.handleResultSets(ps);
  }

PreparedStatement 封装的是已经预编译好的 Sql. 实际上, ps 是 HikariProxyPreparedStatement 类型(默认连接池是 HikariCP ,所以就是这个类), 而 HikariProxyPreparedStatement 又继承自 ProxyPreparedStatement, ProxyPreparedStatement 实现了 PreparedStatement.

从这里开始, 就进入连接池和驱动的部分了, 暂不继续分析.

至此, MyBatis 中 select 的完整执行过程就结束了. 这个过程中还有很多细节待挖掘, 比如 sql 参数是怎么处理的, 结果集是怎么处理的,等等. 在后续的系列文章中都会一一展开详解.

总结

  1. MyBatis 中使用动态代理来代理我们定义的 Mapper 接口, 屏蔽了所有的实现细节, 对使用者很友好;
  2. MyBatis 中大量使用了代理和组合模式,比如 CachingExecutor 与 SimpleExecutor 的关系;
  3. MyBatis 中大量使用了模板模式,比如 BaseExecutor 与 SimpleExecutor 的关系.

本文链接http://www.dzjqx.cn/news/show-617223.html