1. 程式人生 > >Mybatis 快取系統原始碼解析

Mybatis 快取系統原始碼解析

本文從以下幾個方面介紹:

相關文章

前言

快取的相關介面

一級快取的實現過程

二級快取的實現過程

如何保證快取的執行緒安全

快取的裝飾器

相關文章

Mybatis 解析 SQL 原始碼分析二

Mybatis Mapper.xml 配置檔案中 resultMap 節點的原始碼解析

Mybatis 解析 SQL 原始碼分析一

Mybatis Mapper 介面原始碼解析(binding包)

Mybatis 資料來源和資料庫連線池原始碼解析(DataSource)

Mybatis 型別轉換原始碼分析

Mybatis 解析配置檔案的原始碼解析

前言

在使用諸如 Mybatis 這種 ORM 框架的時候,一般都會提供快取功能,用來快取從資料庫查詢到的結果,當下一次查詢條件相同的時候,只需從快取中進行查詢返回即可,如果快取中沒有,再去查庫;一方面是提高查詢速度,另一方面是減少資料庫壓力;Mybatis 也提供了快取,它分為一級快取和二級快取,接下來就來看看它的快取系統是如何實現的。

快取系統的實現使用了  模板方法模式 和 裝飾器模式

接下來先來看下和快取相關的介面

Cache

Mybatis 使用 Cache 來表示快取,它是一個介面,定義了快取需要的一些方法,如下所示:


public interface Cache {
  //獲取快取的id,即 namespace
  String getId();
  // 新增快取
  void putObject(Object key, Object value);
  //根據key來獲取快取對應的值
  Object getObject(Object key);
  // 刪除key對應的快取
  Object removeObject(Object key);
  // 清空快取  
  void clear();
  // 獲取快取中資料的大小
  int getSize();
  //取得讀寫鎖, 從3.2.6開始沒用了
  ReadWriteLock getReadWriteLock();
}

對於每一個 namespace 都會建立一個快取的例項,Cache 實現類的構造方法都必須傳入一個 String 型別的ID,Mybatis自身的實現類都使用 namespace 作為 ID

PerpetualCache

Mybatis 為 Cache 介面提供的唯一一個實現類就是 PerpetualCache,這個唯一併不是說 Cache 只有一個實現類,只是快取的處理邏輯,Cache 還有其他的實現類,但是隻是作為裝飾器存在,只是對 Cache 進行包裝而已。

PerpetualCache 的實現比較簡單,就是把對應的 key-value 快取資料存入到 map 中,如下所示:

public class PerpetualCache implements Cache {
  // id,一般對應mapper.xml 的namespace 的值
  private String id;
  
  // 用來存放資料,即快取底層就是使用 map 來實現的
  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }
  //......其他的getter方法.....
  // 新增快取
  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }
  // 獲取快取
  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }
  // 刪除快取
  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }
  // 清空快取
  @Override
  public void clear() {
    cache.clear();
  }
}

從上面的程式碼邏輯可以看到,mybatis 提供的快取底層就是使用一個 HashMap 來實現的,但是我們知道,HashMap 不是執行緒安全的,它是如何來保證快取中的執行緒安全問題呢?在後面講到 Cache 的包裝類就知道,它提供了一個 SynchronizedCache 的裝飾器類,就是用來包裝執行緒安全的,在該類中所有方法都加上了 synchronized 關鍵字。

CacheKey

Mybatis 的快取使用了 key-value 的形式存入到 HashMap 中,而 key 的話,Mybatis 使用了 CacheKey 來表示 key,它的生成規則為:mappedStementId + offset + limit + SQL + queryParams + environment生成一個雜湊碼.

public class CacheKey implements Cloneable, Serializable {

  private static final int DEFAULT_MULTIPLYER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  // 參與計算hashcode,預設值為37
  private int multiplier;
  // CacheKey 物件的 hashcode ,預設值 17
  private int hashcode;
  // 檢驗和 
  private long checksum;
  // updateList 集合的個數
  private int count;
  // 由該集合中的所有物件來共同決定兩個 CacheKey 是否相等
  private List<Object> updateList;

  public int getUpdateCount() {
    return updateList.size();
  }
  // 呼叫該方法,向 updateList 集合新增對應的物件
  public void update(Object object) {
    if (object != null && object.getClass().isArray()) {
      // 如果是陣列,則迴圈處理每一項
      int length = Array.getLength(object);
      for (int i = 0; i < length; i++) {
        Object element = Array.get(object, i);
        doUpdate(element);
      }
    } else {
      doUpdate(object);
    }
  }
  // 計算 count checksum hashcode 和把物件新增到 updateList 集合中
  private void doUpdate(Object object) {
    int baseHashCode = object == null ? 1 : object.hashCode();
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
  }
 
  // 判斷兩個 CacheKey 是否相等
  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (!(object instanceof CacheKey)) {
      return false;
    }

    final CacheKey cacheKey = (CacheKey) object;

    if (hashcode != cacheKey.hashcode) {
      return false;
    }
    if (checksum != cacheKey.checksum) {
      return false;
    }
    if (count != cacheKey.count) {
      return false;
    }
    // 如果前幾項都不滿足,則迴圈遍歷 updateList 集合,判斷每一項是否相等,如果有一項不相等則這兩個CacheKey不相等
    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (thisObject == null) {
        if (thatObject != null) {
          return false;
        }
      } else {
        if (!thisObject.equals(thatObject)) {
          return false;
        }
      }
    }
    return true;
  }

  @Override
  public int hashCode() {
    return hashcode;
  }
}

如果需要進行快取,則如何建立 CacheKey 呢?下面這個就是建立 一個 CacheKey 的方法:

  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    //cacheKey 物件 
    CacheKey cacheKey = new CacheKey();
    // 向 updateList 存入id
    cacheKey.update(ms.getId());
    // 存入offset
    cacheKey.update(rowBounds.getOffset());
    // 存入limit
    cacheKey.update(rowBounds.getLimit());
    // 存入sql
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
          String propertyName = parameterMapping.getProperty();
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          Object  value = metaObject.getValue(propertyName);
          // 存入每一個引數
          cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // 存入 environmentId
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

從上面 CacheKey 和建立 CacheKey 的程式碼邏輯可以看出,Mybatis 的快取使用了 mappedStementId + offset + limit + SQL + queryParams + environment 生成的hashcode作為 key。

瞭解了上述和快取相關的介面後,接下來就來看看 Mybatis 的快取系統是如何實現的,Mybatis 的快取分為一級快取和二級快取,一級快取是在 BaseExecutor 中實現的,二級快取是在 CachingExecutor 中實現的。

Executor

Executor 介面定義了操作資料庫的基本方法,SqlSession 的相關方法就是基於 Executor 介面實現的,它定義了操作資料庫的方法如下:

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  // insert | update | delete 的操作方法
  int update(MappedStatement ms, Object parameter) throws SQLException;
 
  // 查詢,帶分頁,帶快取  
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  // 查詢,帶分頁 
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  // 查詢儲存過程
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  //重新整理批處理語句
  List<BatchResult> flushStatements() throws SQLException;

  // 事務提交
  void commit(boolean required) throws SQLException;
  // 事務回滾
  void rollback(boolean required) throws SQLException;

  // 建立快取的key
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  // 是否快取
  boolean isCached(MappedStatement ms, CacheKey key);
  // 清空快取
  void clearLocalCache();
  // 延遲載入
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
  // 獲取事務
  Transaction getTransaction();
}

一級快取

BaseExecutor

BaseExecutor 是一個抽象類,實現了 Executor 介面,並提供了大部分方法的實現,只有 4 個基本方法:doUpdate,  doQuery,  doQueryCursor,  doFlushStatement 沒有實現,還是一個抽象方法,由子類實現,這 4 個方法相當於模板方法中變化的那部分。

Mybatis 的一級快取就是在該類中實現的。

Mybatis 的一級快取是會話級別的快取,Mybatis 每建立一個 SqlSession 物件,就表示開啟一次資料庫會話,在一次會話中,應用程式很可能在短時間內反覆執行相同的查詢語句,如果不對資料進行快取,則每查詢一次就要執行一次資料庫查詢,這就造成資料庫資源的浪費。又因為通過 SqlSession 執行的操作,實際上由 Executor 來完成資料庫操作的,所以在 Executor 中會建立一個簡單的快取,即一級快取;將每次的查詢結果快取起來,再次執行查詢的時候,會先查詢一級快取,如果命中,則直接返回,否則再去查詢資料庫並放入快取中。

一級快取的生命週期與 SqlSession 的生命週期相同,當呼叫 Executor.close 方法的時候,快取變得不可用。一級快取是預設開啟的,一般情況下不需要特殊的配置,如果需要特殊配置,則可以通過外掛的形式來實現

public abstract class BaseExecutor implements Executor {
  // 事務,提交,回滾,關閉事務
  protected Transaction transaction;
  // 底層的 Executor 物件
  protected Executor wrapper;
  // 延遲載入佇列
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  // 一級快取,用於快取查詢結果
  protected PerpetualCache localCache;
  // 一級快取,用於快取輸出型別引數(儲存過程)
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;
  // 用來記錄巢狀查詢的層數
  protected int queryStack;
  private boolean closed;

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

// 4 個抽象方法,由子類實現,模板方法中可變部分
  protected abstract int doUpdate(MappedStatement ms, Object parameter)throws SQLException;
  protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;
  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)throws SQLException;

  // 執行 insert | update | delete 語句,呼叫 doUpdate 方法實現,在執行這些語句的時候,會清空快取
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    // ....
    // 清空快取
    clearLocalCache();
    // 執行SQL語句
    return doUpdate(ms, parameter);
  }

  // 重新整理批處理語句,且執行快取中還沒執行的SQL語句
  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }
  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    // ...
    // doFlushStatements 的 isRollBack 引數表示是否執行快取中的SQL語句,false表示執行,true表示不執行
    return doFlushStatements(isRollBack);
  }
  
  // 查詢儲存過程
  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }

  // 事務的提交和回滾
  @Override
  public void commit(boolean required) throws SQLException {
    // 清空快取
    clearLocalCache();
    // 重新整理批處理語句,且執行快取中的QL語句
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }
  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        // 清空快取
        clearLocalCache();
        // 重新整理批處理語句,且不執行快取中的SQL
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

在上面的程式碼邏輯中,執行update型別的語句會清空快取,且執行結果不需要進行快取,而在執行查詢語句的時候,需要對資料進行快取,如下所示:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 獲取查詢SQL
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 建立快取的key,建立邏輯在 CacheKey中已經分析過了
    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 {
    // ....
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 如果不是巢狀查詢,且 <select> 的 flushCache=true 時才會清空快取
      clearLocalCache();
    }
    List<E> list;
    try {
      // 巢狀查詢層數加1
      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--;
    }
    // ... 處理延遲載入的相關邏輯
    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;
  }

二級快取

Mybatis 提供的二級快取是應用級別的快取,它的生命週期和應用程式的生命週期相同,且與二級快取相關的配置有以下 3 個:

1. mybatis-config.xml 配置檔案中的 cacheEnabled 配置,它是二級快取的總開關,只有該配置為 true ,後面的快取配置才會生效。預設為 true,即二級快取預設是開啟的。

2. Mapper.xml 配置檔案中配置的 <cache> 和 <cache-ref>標籤,如果 Mapper.xml 配置檔案中配置了這兩個標籤中的任何一個,則表示開啟了二級快取的功能,在 Mybatis 解析 SQL 原始碼分析一 文章中已經分析過,如果配置了 <cache> 標籤,則在解析配置檔案的時候,會為該配置檔案指定的 namespace 建立相應的 Cache 物件作為其二級快取(預設為 PerpetualCache 物件),如果配置了 <cache-ref> 節點,則通過 ref 屬性的namespace值引用別的Cache物件作為其二級快取。通過 <cache> 和 <cache-ref> 標籤來管理其在namespace中二級快取功能的開啟和關閉

3. <select> 節點中的 useCache 屬性也可以開啟二級快取,該屬性表示查詢的結果是否要存入到二級快取中,該屬性預設為 true,也就是說 <select> 標籤預設會把查詢結果放入到二級快取中。

 

 

Mybatis 的二級快取是用 CachingExecutor 來實現的,它是 Executor 的一個裝飾器類。為 Executor 物件添加了快取的功能。

在介紹 CachingExecutor 之前,先來看看 CachingExecutor 依賴的兩個類,TransactionalCacheManager 和 TransactionalCache。

TransactionalCache

TransactionalCache 實現了 Cache 介面,主要用於儲存在某個 SqlSession 的某個事務中需要向某個二級快取中新增的資料,程式碼如下:

public class TransactionalCache implements Cache {
  // 底層封裝的二級快取對應的Cache物件
  private Cache delegate;
  // 為true時,表示當前的 TransactionalCache 不可查詢,且提交事務時會清空快取
  private boolean clearOnCommit;
  // 存放需要新增到二級快取中的資料
  private Map<Object, Object> entriesToAddOnCommit;
  // 存放為命中快取的 CacheKey 物件
  private Set<Object> entriesMissedInCache;

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

  // 新增快取資料的時候,先暫時放到 entriesToAddOnCommit 集合中,在事務提交的時候,再把資料放入到二級快取中,避免髒資料
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }
  // 提交事務,
  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    // 把 entriesToAddOnCommit  集合中的資料放入到二級快取中
    flushPendingEntries();
    reset();
  }
 // 把 entriesToAddOnCommit  集合中的資料放入到二級快取中
  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      // 放入到二級快取中
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }
 // 事務回滾
 public void rollback() {
    // 把未命中快取的資料清除掉
    unlockMissedEntries();
    reset();
  }
  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
        delegate.removeObject(entry);
    }
  }

TransactionalCacheManager

TransactionalCacheManager 用於管理 CachingExecutor 使用的二級快取:

public class TransactionalCacheManager {
 
  //用來管理 CachingExecutor 使用的二級快取
  // key 為對應的CachingExecutor 使用的二級快取
  // value 為對應的 TransactionalCache 物件
  private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
  
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }  
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }
  // 所有的呼叫都會呼叫 TransactionalCache 的方法來實現
  private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }

}

CachingExecutor

接下來看下 二級快取的實現 CachingExecutor :

public class CachingExecutor implements Executor {
  // 底層的 Executor
  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager();

  // 查詢方法
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 獲取 SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 建立快取key,在CacheKey中已經分析過建立過程
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  // 查詢
  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) {
      // 根據 <select> 的屬性 useCache 的配置,決定是否需要清空二級快取
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        // 二級快取不能儲存輸出引數,否則拋異常
        ensureNoOutParams(ms, parameterObject, boundSql);
        // 從二級快取中查詢對應的值
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 如果二級快取沒有命中,則呼叫底層的 Executor 查詢,其中會先查詢一級快取,一級快取也未命中,才會去查詢資料庫
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 查詢到的資料放入到二級快取中去
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 如果沒有開啟二級快取,則直接呼叫底層的 Executor 查詢,還是會先查一級快取
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

以上就是 Mybatis 的二級快取的主要實現過程,CachingExecutor , TransactionalCacheManager 和 TransactionalCache 的關係如下所示,主要是通過 TransactionalCache 來操作二級快取的。

此外,CachingExecutor 還有其他的一些方法,主要是呼叫底層封裝的 Executor 來實現的。

以上就是 Mybatis 的一級快取和二級快取的實現過程。

Cache 裝飾器

在介紹 Cache 介面的時候,說到,Cache 介面由很多的裝飾器類,共 10 個,添加了不同的功能,如下所示:

來看看 SynchronizedCache 裝飾器類吧,在上面的快取實現中介紹到了 Mybatis 其實就是使用 HashMap 來實現快取的,即把資料放入到 HashMap中,但是 HashMap 不是線安全的,Mybatis 是如何來保證快取中的執行緒安全問題呢?就是使用了 SynchronizedCache 來保證的,它是一個裝飾器類,其中的方法都加上了 synchronized 關鍵字:

public class SynchronizedCache implements Cache {

  private Cache delegate;
  
  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }
  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }
  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }
  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }
  // ............
}

接下來看下新增 Cache 裝飾器的方法,在 CacheBuilder.build() 方法中進行新增:

public class CacheBuilder {
  //...........
  // 建立快取
  public Cache build() {
    // 設定快取的實現類
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // 新增裝飾器類
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      // 為 Cache 新增裝飾器
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
  // 設定 Cache 的預設實現類為 PerpetualCache
  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }
  // 新增裝飾器
  private Cache setStandardDecorators(Cache cache) {
    try {
      // 新增 ScheduledCache 裝飾器
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      // 新增SerializedCache裝飾器
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      // 新增 LoggingCache 裝飾器
      cache = new LoggingCache(cache);
      // 新增  SynchronizedCache 裝飾器,保證執行緒安全
      cache = new SynchronizedCache(cache);
      if (blocking) {
        // 新增 BlockingCache 裝飾器
        cache = new BlockingCache(cache);
      }
      return cache;
  }
}

還有其他的裝飾器,這裡就不一一列出來了。

到這裡 Mybatis 的快取系統模組就分析完畢了。