Mybatis源码阅读(三)

211 阅读6分钟

前言

回顾下之前映射文件的解析,最终会生成一个sqlSource,而一个sqlSource中有一个sqlNode的集合,而sqlNode封装的就是每一个sql脚本,同时不同动态标签对应不同的sqlNode。而sqlSource提供了一个getBoundSql的方法,返回的是一个可以由jdbc直接执行的sql语句。对sql中#{}和${}的解析在对应的sqlSource中,#{}由RawSqlSource而${}由DynamicSqlSource解析。本篇文件我们就来看下具体sql的执行流程。

MybatisSql的执行流程

阅读源码之前我们先看下mybatis的一个执行过程:

我们都知道sql的执行都是从sqlSession开始的,提供最顶层的api接口,而执行器Ececutor是比较重要的一部分,主要用于缓存的查询和动态sql的生成。最终执行数据库查询是在StatementHandler中。下面我们看下Executor的一个继承体系。

  • CachingExecutor:主要是用来处理二级缓存的执行器,如果没有二级缓存就会通过委托模式把具体的执行过程给BaseExecutor执行。下
  • BaseExecutor:主要用来处理一级缓存,如果没有缓存就将查询任务交给他的实现类去执行。

清楚了这些之后我们开始进入具体的源码阅读过程。因为SqlSession是一个接口,因此我们从我们从它的默认实现类DefaultSession中去找到查询的入口,找到DefaultSqlSession中的selectList方法

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
		try {
			// 从configuration通过id获取我们解析好的MappedStatement对象
			MappedStatement ms = configuration.getMappedStatement(statement);
			// 调用执行器的查询方法
			// RowBounds是用来处理分页逻辑,在内存中分页
			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();
		}
	}

而这个执行器是从构造方法传入的,我们可以从DefaultSqlSessionFactory的openSessionFromDataSource方法看到其实执行器是由configuration创建出来的

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取数据源环境
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 获取JdbcTransaction或者ManagedTransaction
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建Executor执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

接着我们可以再跟进去看下configuration是怎么去创建执行器的

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
		executorType = executorType == null ? defaultExecutorType : executorType;
		executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
		Executor executor;
		if (ExecutorType.BATCH == executorType) {
			// 批处理执行器
			executor = new BatchExecutor(this, transaction);
		} else if (ExecutorType.REUSE == executorType) {
			// 可重复用执行器
			executor = new ReuseExecutor(this, transaction);
		} else {
			// 简单执行器
			executor = new SimpleExecutor(this, transaction);
		}
		// 如果开启缓存(默认是开启的),则使用缓存执行器
		if (cacheEnabled) {
			executor = new CachingExecutor(executor);
		}
		// 插件执行
		executor = (Executor) interceptorChain.pluginAll(executor);
		return executor;
	}

在这里我们就可以清楚的看到mybats是怎么创建具体的执行器,通过我们配置的执行器类型和是否开启缓存去判断使用哪个执行器。我们接下来带大家看的是CachingExecutor的一个处理过程。下面是CachingExecutor的query方法

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
	// 获取mybatis的二级缓存
    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) {
          // 委托给BaseExecutor执行
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 委托给BaseExecutor执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

从这段代码我们可以很清晰的看到二级缓存的一个执行流程,就是先从缓存查,如果没有查到就去委托BaseExecutor去查询,下面我们看下BaseExecutor的query方法

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) {
        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();
      }
     
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        
        clearLocalCache();
      }
    }
    return list;
  }

从这段代码我们就能看到mybatis对一级缓存的使用,其实一级缓存localCache里面封装的就是一个map,所以一级缓存就是通过map实现的。最终如果没有从缓存中查询到数据,那么mybatis就会去查询数据库。接着我们看下具体的queryFromDatabase方法

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

通过doQuery方法查询数据库,查询完之后加到一级缓存中。而这个doQuery方法是BaseExecutor子类实现的方法。我们这次看的是SimpleExecutor的doQuery方法。

Statement stmt = null;
		try {
			Configuration configuration = ms.getConfiguration();
			// RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler)
			StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
					resultHandler, boundSql);
			// 设置参数
			stmt = prepareStatement(handler, ms.getStatementLog());
			// 执行SQL语句,并且映射结果集
			return handler.query(stmt, resultHandler);
		} finally {
			closeStatement(stmt);
		}

这里通过configuration获取StatementHandler,我们知道jdbc有多种statement,而不同的statement有不同的处理,因此mybatis通过创建RoutingStatementHandler的方法去解决这个问题。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
			Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
		// 创建路由功能的StatementHandler,根据MappedStatement中的StatementType
		StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
				rowBounds, resultHandler, boundSql);
		statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
		return statementHandler;
	}

其实这个RoutingStatementHandler里面什么都没有做,只是在创建的时候确定了具体的statementHandler。

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

  }

而最终是通过类似委托的方式,调用具体的statementHandler去执行。上面的query方法就是通过具体的statementHandler去执行的,这里我们看下PreparedStatementHandler的query方法。

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行PreparedStatement,也就是执行SQL语句
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

这里我们就看到了这个execute就是真正的查询数据库的方法。然后通过DefaultResultSetHandler去处理结果集并返回。至此我们mybatis的执行流程的阅读就结束了,其实整个流程也是比较清晰的。

最终,我们通过这三篇文章我们把mybatis的源码主流程基本上都阅读完毕了,其中还有一些其他的分支,比如,通过注解加载的过程,还有最后结果集的封装过程,大家都可以自行往下阅读。mybatis结束之后,接下来会跟大家分享下spring的源码阅读,谢谢观看。