深入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 的緩存系統模塊就分析完畢了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章