mybatis兩級緩存原理剖析

https://blog.csdn.net/zhurhyme/article/details/81064108sql

對於mybatis的緩存認識一直有一個誤區,因此今天寫一篇文章幫本身訂正一下。mybatis的緩存分一級緩存與二級緩存。下面分別對這兩種緩存進行詳細解說。
mybatis 的調用鏈

首先咱們須要大體的瞭解一下mybatis的調用鏈,而後才能更好的理解mybatis的緩存。
主要的api羅列以下:

public interface SqlSessionFactory {    
  SqlSession openSession();
}


public class DefaultSqlSessionFactory implements SqlSessionFactory {

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

         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);
          final Executor executor = configuration.newExecutor(tx, execType);//重點是這個方法
          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); //mybatis的插件機制
        return executor;
      }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

上面的代碼大體描述了,咱們爲了獲取一個sqlSession實例的過程。下面的方法爲SqlSession的實現類DefaultSqlSession中的一個查詢方法.

     @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

從該方法咱們可知SqlSession的方法實現所有委託給了Executor實例。Executor接口的類圖:

這裏寫圖片描述
mybatis 一級緩存

上面的executor.query如何執行呢?public abstract class BaseExecutor 中的方法給了咱們答案.

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  @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;
  }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

請注意localCache是什麼呢?在BaseExecutor的構造器有這麼一句代碼: this.localCache = new PerpetualCache(「LocalCache」); 即locaCache爲緩存,即mybatis的一級緩存。

localCache生命週期是與SqlSession的生命週期是同樣;不一樣的sqlSession會生成不一樣的localCache.這就是mybatis的一級緩存,它是與sqlSession息息相關,只能單個sqlSession實例獨享。而且默認是開啓的,沒有開關能夠關閉它。可是它的使用很是有侷限。因此mybatis才須要在二級緩存,即突破SqlSession範圍的緩存。mybatis的二級緩存與咱們一般認知的緩存沒有區別。
mybatis 的二級緩存

mybatis的二級緩存即經過CachingExecutor來實現。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

即當cache不爲null時,則從tcm中獲取,若是獲取不到則再從delegate中獲取。
如今關鍵是tcm.getObject(cache, key);
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

TransactionalCacheManager的部分源代碼

    public class TransactionalCacheManager {

      private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

      public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
      }


      private TransactionalCache getTransactionalCache(Cache cache) {
    /*
    關鍵代碼,它的做用從transactionalCaches 中獲取TransactionalCache ,若是沒有,則將cache做爲參數,從新new一個,這段代碼涉及jdk8的新語法及以及新特性,
    不太容易懂,若是有小朋友看不懂,我建議你好好學習一下jdk8
    */  
        return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
      }

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

TransactionalCache 部分源碼

     public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<>();
        this.entriesMissedInCache = new HashSet<>();
      }

      @Override
      public Object getObject(Object key) {
        // issue #116
        Object object = delegate.getObject(key);
        if (object == null) {
          entriesMissedInCache.add(key);
        }
        // issue #146
        if (clearOnCommit) {
          return null;
        } else {
          return object;
        }
      }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

峯迴路轉

Cache cache = ms.getCache(); //這句是重點

    1

即只要MappedStatement ms 本身有cache,則直接使用,這個就是mybatis的二級緩存。關於開啓mybatis的二級緩存,則須要在config中配置cacheEnabled 爲ture,它的默認值即爲true。
另外須要在mapper中配置

<cache />

    1

這樣就能夠最簡單的方式使用mybatis的二級緩存了。至於如何擴展mybatis的二級緩存,不在本文之列。
mybatis 二級緩存絮叨

至此mybatis 的二級緩存好像已經說明白了。可是還想在此絮叨一下,緣由是爲了更好的理解什麼是緩存。mybatis的二級緩存cache它是跟在MappedStatement上的。咱們設置 cache標籤,是以mapper爲單位的。即這個mapper文件內的statement 共用這個cache標籤。cache它與sqlSession沒有任何關係,即不一樣的sqlSession執行同一個statement就能夠共用同一個cache實例了。

那麼是否有更大的cache使用範圍呢?答案是有的,舉個栗子。 部門表被cache;人員表被cache,從上面咱們可知,這是兩個不一樣的cache實例。在人員mapper中如何出人員與部門的聯合查詢,則當部門更新時,會出現局部的數據不一致。如何解決這個問題呢?一種答案是去掉緩存;另外一種就是mybatis給咱們提供的

<cache-ref >

    1

標籤,它可使多個mapper使用同一個cache實例,從而達到緩存數據的一致性(關於緩存數據的一致性,性能,緩存數據範圍,在此不討論)。
---------------------
做者:zhurhyme
來源:CSDN
原文:https://blog.csdn.net/zhurhyme/article/details/81064108
版權聲明:本文爲博主原創文章,轉載請附上博文連接!api

相關文章
相關標籤/搜索