Mybatis 緩存系統源碼解析

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

相關文章java

前言sql

緩存的相關接口數據庫

一級緩存的實現過程數組

二級緩存的實現過程緩存

如何保證緩存的線程安全安全

緩存的裝飾器mybatis

相關文章

Mybatis 解析 SQL 源碼分析二app

Mybatis Mapper.xml 配置文件中 resultMap 節點的源碼解析框架

Mybatis 解析 SQL 源碼分析一ide

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

相關文章
相關標籤/搜索