Mybatis源码分析(六):SQL执行器Executor与一二级缓存的实现

版权声明:非商业目的可自由转载,转载请标明出处 https://blog.csdn.net/u010013573/article/details/87998919

SQL执行器Executor

  • 由上篇文章分析可知,mybatis在内部主要通过executor包的Executor来调用JDBC的相关API来完成SQL语句的执行。Executor通常绑定到SqlSession中,即Executor作为SqlSession对象的一个内部属性,故Executor的对象创建是与SqlSession的对象创建一起的。

  • SqlSession对象是由SqlSessionFactory来创建的,所以SqlSessionFactory在创建SqlSession对象时,也会创建对应的Executor对象。DefaultSqlSessionFactory的具体如下:

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          
          // 创建SQL执行器executor
          final Executor executor = configuration.newExecutor(tx, execType);
          
          // 将配置configuration,SQL执行器executor作为参数SqlSession对象
          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创建Executor对象的newExecutor方法实现如下:如果在mybatisConfig.xml配置了cacheEnabled为true,则使用Executor接口的CachingExecutor实现类,其中CachingExecutor为使用二级缓存的SQL执行器。而一级缓存是默认开启的。

    // SQL执行器,主要负责解析参数,调用底层的JDBC接口来执行SQL
    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);
        }
    
        // 如果是开启了二级缓存,则使用CachingExecutor来包装该executor
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
    
  • 缓存主要是在查询select操作中使用。

一级缓存实现

  • 一级缓存是默认开启的,且不能通过配置来显式关闭一级缓存(不过可以设置一级缓存级别为STATEMENT来关闭),所以是在顶层抽象基类BaseExecutor中定义,核心实现如下:其中一级缓存为使用HashMap的包装类caching包的PerpetualCache来实现。

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 缓存key的生成
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
    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) {
            // 从缓存获取指定SQL的结果,而不用去数据库查询
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 去数据库查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
    
        // 由于SqlSession不是线程安全的,故任何时候只存在一个线程操作,故queryStack总是0的
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
    
          // 如果是STATEMENT,则每次执行完查询都清空缓存,故其实是没有缓存
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
    }
    
  • 缓存key的生成算法如下:

    // 缓存key
    @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;
    }
    

二级缓存实现

  • 由以上分析,如果开启了二级缓存,则SqlSessionFactory在创建SqlSession对象时,同步创建的是类型为CachingExecutor的SQL执行器,故后续通过该SqlSession执行的查询操作都是通过该CachingExecutor对象来执行SQL调用,CachingExecutor的查询方法query定义如下:

    // 在Executor级别实现二级缓存,这是一个包装类,由所有executor共享,
    // 所以所有executor,即所有SqlSession共享这个Executor提供的缓存
    public class CachingExecutor implements Executor {
    
      // 被包装的实际执行的executor
      private final Executor delegate;
    
      // 二级缓存管理器,内部维护了各个namespace对应的二级缓存
      private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    
      ...
    
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        // 获取该语句对应的namespace关联的二级缓存
        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);
    
            // 二级缓存没有,则交给被包装的executor去查询,即可能是从一级缓存获取,如果一级缓存也没有,则查询数据库
            if (list == null) {
              list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        
        // 如果缓存没有,则执行数据库查询获取
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
      
      ...
        
    }
    

猜你喜欢

转载自blog.csdn.net/u010013573/article/details/87998919