Mybatis的緩存

Mybatis的緩存

mybatis是一個查詢數據庫的封裝框架,主要是封裝提供靈活的增刪改sql,開發中,service層可以經過mybatis組件查詢和修改數據庫中表的數據;做爲查詢工具,mybatis有使用緩存,這裏講一下mybatis的緩存相關源碼。java

緩存sql

在計算機裏面,任何信息都有源頭,緩存通常指源頭信息讀取後,放在內存或者其餘讀取較快的地方,下次讀取相同信息不去源頭查詢而是直接從內存(或者能快速存取的硬件)讀取。這樣能夠減小硬件使用,提升讀取速度。數據庫

mybatis也是這樣,查詢數據庫的數據以後,mybatis能夠把查詢結果緩存到內存,下次查詢若是查詢語句相同,而且查詢相關的表的數據沒被修改過,就能夠直接返回緩存中的結果,而不用去查詢數據庫的語句,有效節省了時間。apache

簡單看一下mybatis一級緩存和二級緩存相關源碼,學習使用緩存

一級緩存

經過查看源碼可知,一級緩存是綁定sqSsession中的,因此每次查詢sqlSession不一樣就失效,相同的sqlSession可使用一級緩存。session

mybatis默認sqlsession:org.apache.ibatis.session.defaults.DefaultSqlSessionmybatis

構造方法中傳入executor(查詢執行對象)app

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

executor中攜帶一級緩存成員:框架

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

查詢使用一級緩存邏輯

org.apache.ibatis.executor.BaseExecutor.query()ide

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());
    
    List<E> list;
    try {
      queryStack++;
      	//localCache 一級緩存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        //先從一級緩存中獲取,key是經過sql語句生成
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 若是緩存中沒有 才從數據庫查詢
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    return list;
  }

  //從數據庫讀取數據
  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;
  }

二級緩存

二級緩存mapper中的,默認是開啓的,但須要在映射文件mapper.xml中添加<cache/>標籤

<mapper namespace="userMapper">
	<cache/><!-- 添加cache標籤表示此mapper使用二級緩存 -->
</mapper>

配置false能夠關閉二級緩存

mybatis:
  configuration:
     cache-enabled: false #默認值爲true,表示開啓

二級緩存的解析

org.apache.ibatis.builder.xml.XMLMapperBuilder

private void configurationElement(XNode context) {
    try {
      //...
      cacheElement(context.evalNode("cache")); //解析cache標籤
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void cacheElement(XNode context) {
    if (context != null) { // if hava cache tag 若是有cache標籤才執行下面的邏輯
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);//創建二級緩存
    }
  }

org.apache.ibatis.builder.MapperBuilderAssistant.useNewCache():

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);//二級緩存賦值,若是cache標籤爲空,不會執行此方法,currentCache爲空
    currentCache = cache; 
    return cache;
  }

在映射文件mapper中若是沒有cache標籤,不會執行上面的useNewCache方法,cache爲null,就不會使用二級緩存(至關於失效)。

查詢使用二級緩存邏輯

org.apache.ibatis.executor.CachingExecutor :

@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) {//若是二級緩存對象不爲空 嘗試在二級緩存中獲取(沒有cache標籤此對象就是空)
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key); //從二級緩存中獲取數據
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //若是爲空,使用delegate查詢(BaseExecutor)
          tcm.putObject(cache, key, list); // 查詢結果保存到二級緩存
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

二級緩存和一級緩存不用想,數據庫的數據被修改是要清空緩存的,否則數據有誤,至於怎麼清空,是另外一套邏輯了,mapper中的cache標籤能夠配置一些參數,好比緩存按期清空。

一級二級緩存前後順序

mybatis默認是先查詢二級緩存,沒有,再查看一級緩存,都爲空,最後查詢數據庫

相關文章
相關標籤/搜索