MyBatis一级和二级缓存

一级缓存创建时机

/**
 * DefaultSqlSessionFactory#openSession
 */
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);
    // 创建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();
  }
}

创建SqlSession时调用了这个方法创建了DefaultSqlSession,创建Executor执行器会生成一级缓存

/**
 * Configuration#newExecutor
 */
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;
}

如果开启了二级缓存(二级缓存的全局开关,默认是开启的),会把执行器包装成缓存执行器CachingExecutor,但真正执行操作的还是BatchExecutor、ReuseExecutor、SimpleExecutor

protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<>();
  // 一级缓存
  this.localCache = new PerpetualCache("LocalCache");
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
}

BatchExecutor、ReuseExecutor、SimpleExecutor都继承了BaseExecutor类,所以在创建时会调用父类构造器,这时会创建一级缓存。一级缓存就是BaseExecutor的localCache属性,所以也无法关闭一级缓存。

二级缓存创建时机

/**
 * MapperAnnotationBuilder#parse
 */
public void parse() {
  // 获取mapper接口的全路径
  String resource = type.toString();
  // 是否解析过该mapper接口
  if (!configuration.isResourceLoaded(resource)) {
    // 先解析mapper映射文件
    loadXmlResource();
    // 设置解析标识
    configuration.addLoadedResource(resource);
    // Mapper构建者助手
    assistant.setCurrentNamespace(type.getName());
    // 解析CacheNamespace注解
    parseCache();
    // 解析CacheNamespaceRef注解
    parseCacheRef();
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        // issue #237
        if (!method.isBridge()) {
      	  // 每个mapper接口中的方法,都解析成MappedStatement对象
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

在解析Mapper接口时,如果有CacheNamespace或者CacheNamespaceRef注解的话会创建二级缓存。CacheNamespace是创建自己的缓存,CacheNamespaceRef是指向其他Mapper接口的缓存。

缓存使用

/**
 * CachingExecutor#query
 */
@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) {
        // 委托给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);
}

在查询时,如果开启了二级缓存(默认是开启的),执行器也就是CachingExecutor,会先去二级缓存中查询,有数据就直接返回;如果没有二级缓存,或者二级缓存查询不到,就委托真实的执行器查询

/**
 * BaseExecutor#query
 */
@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) {
      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();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

所以,Mybatis查询顺序:二级缓存 → 一级缓存 → 数据库。

根据源码可以发现,一级缓存是SqlSession级别,二级缓存是Mapper接口级别的。一级缓存是没办法关闭;二级缓存关闭可以把全局开关cacheEnabled设置成 false,或者Mapper接口不加CacheNamespace或者CacheNamespaceRef注解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值