91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Mybatis緩存模塊的示例分析

發布時間:2021-12-15 15:09:15 來源:億速云 閱讀:170 作者:小新 欄目:大數據

這篇文章給大家分享的是有關Mybatis緩存模塊的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

1 Cache 組件

MyBatis 中緩存模塊相關的代碼位于 org.apache.ibatis.cache 包 下,其中 Cache 接口 是緩存模塊中最核心的接口,它定義了所有緩存的基本行為。

public interface Cache {  /**   * 獲取當前緩存的 Id   */  String getId();  /**   * 存入緩存的 key 和 value,key 一般為 CacheKey對象   */  void putObject(Object key, Object value);  /**   * 根據 key 獲取緩存值   */  Object getObject(Object key);  /**   * 刪除指定的緩存項   */  Object removeObject(Object key);  /**   * 清空緩存   */  void clear();  /**   * 獲取緩存的大小   */  int getSize();  /**   * !!!!!!!!!!!!!!!!!!!!!!!!!!   * 獲取讀寫鎖,可以看到,這個接口方法提供了默認的實現!!   * 這是 Java8 的新特性!!只是平時開發時很少用到!!!   * !!!!!!!!!!!!!!!!!!!!!!!!!!   */  default ReadWriteLock getReadWriteLock() {    return null;  }}

如下圖所示,Cache 接口 的實現類有很多,但大部分都是裝飾器,只有 PerpetualCache 提供了 Cache 接口 的基本實現。

Mybatis緩存模塊的示例分析

1.1 PerpetualCache

PerpetualCache(Perpetual:永恒的,持續的)在緩存模塊中扮演著被裝飾的角色,其實現比較簡單,底層使用 HashMap 記錄緩存項,也是通過該 HashMap 對象 的方法實現的 Cache 接口 中定義的相應方法。

public class PerpetualCache implements Cache {  // Cache對象 的唯一標識  private final String id;  // 其所有的緩存功能實現,都是基于 JDK 的 HashMap 提供的方法  private Map<Object, Object> cache = new HashMap<>();  public PerpetualCache(String id) {    this.id = id;  }  @Override  public String getId() {    return id;  }  @Override  public int getSize() {    return cache.size();  }  @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();  }  /**   * 其重寫了 Object 中的 equals() 和 hashCode()方法,兩者都只關心 id字段   */  @Override  public boolean equals(Object o) {    if (getId() == null) {      throw new CacheException("Cache instances require an ID.");    }    if (this == o) {      return true;    }    if (!(o instanceof Cache)) {      return false;    }    Cache otherCache = (Cache) o;    return getId().equals(otherCache.getId());  }  @Override  public int hashCode() {    if (getId() == null) {      throw new CacheException("Cache instances require an ID.");    }    return getId().hashCode();  }}

下面來看一下 cache.decorators 包 下提供的裝飾器,它們都直接實現了 Cache 接口,扮演著裝飾器的角色。這些裝飾器會在 PerpetualCache 的基礎上提供一些額外的功能,通過多個組合后滿足一個特定的需求。

1.2 BlockingCache

BlockingCache 是阻塞版本的緩存裝飾器,它會保證只有一個線程到數據庫中查找指定 key 對應的數據。

public class BlockingCache implements Cache {  // 阻塞超時時長  private long timeout;  // 持有的被裝飾者  private final Cache delegate;  // 每個 key 都有其對應的 ReentrantLock鎖對象  private final ConcurrentHashMap<Object, ReentrantLock> locks;  // 初始化 持有的持有的被裝飾者 和 鎖集合  public BlockingCache(Cache delegate) {    this.delegate = delegate;    this.locks = new ConcurrentHashMap<>();  }}

假設 線程 A 在 BlockingCache 中未查找到 keyA 對應的緩存項時,線程 A 會獲取 keyA 對應的鎖,這樣,線程 A 在后續查找 keyA 時,其它線程會被阻塞。

  // 根據 key 獲取鎖對象,然后上鎖  private void acquireLock(Object key) {    // 獲取 key 對應的鎖對象    Lock lock = getLockForKey(key);    // 獲取鎖,帶超時時長    if (timeout > 0) {      try {        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);        if (!acquired) { // 超時,則拋出異常          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());        }      } catch (InterruptedException e) {        // 如果獲取鎖失敗,則阻塞一段時間        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);      }    } else {      // 上鎖      lock.lock();    }  }  private ReentrantLock getLockForKey(Object key) {    // Java8 新特性,Map系列類 中新增的方法    // V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)    // 表示,若 key 對應的 value 為空,則將第二個參數的返回值存入該 Map集合 并返回    return locks.computeIfAbsent(key, k -> new ReentrantLock());  }

假設 線程 A 從數據庫中查找到 keyA 對應的結果對象后,將結果對象放入到 BlockingCache 中,此時 線程 A 會釋放 keyA 對應的鎖,喚醒阻塞在該鎖上的線程。其它線程即可從 BlockingCache 中獲取 keyA 對應的數據,而不是再次訪問數據庫。

  @Override  public void putObject(Object key, Object value) {    try {      // 存入 key 和其對應的緩存項      delegate.putObject(key, value);    } finally {      // 最后釋放鎖      releaseLock(key);    }  }  private void releaseLock(Object key) {    ReentrantLock lock = locks.get(key);    // 鎖是否被當前線程持有    if (lock.isHeldByCurrentThread()) {      // 是,則釋放鎖      lock.unlock();    }  }

1.3 FifoCache 和 LruCache

在很多場景中,為了控制緩存的大小,系統需要按照一定的規則清理緩存。FifoCache 是先入先出版本的裝飾器,當向緩存添加數據時,如果緩存項的個數已經達到上限,則會將緩存中最老(即最早進入緩存)的緩存項刪除。

public class FifoCache implements Cache {  // 被裝飾對象  private final Cache delegate;  // 用一個 FIFO 的隊列記錄 key 的順序,其具體實現為 LinkedList  private final Deque<Object> keyList;  // 決定了緩存的容量上限  private int size;  // 國際慣例,通過構造方法初始化自己的屬性,緩存容量上限默認為 1024個  public FifoCache(Cache delegate) {    this.delegate = delegate;    this.keyList = new LinkedList<>();    this.size = 1024;  }  @Override  public String getId() {    return delegate.getId();  }  @Override  public int getSize() {    return delegate.getSize();  }  public void setSize(int size) {    this.size = size;  }  @Override  public void putObject(Object key, Object value) {    // 存儲緩存項之前,先在 keyList 中注冊    cycleKeyList(key);    // 存儲緩存項    delegate.putObject(key, value);  }  private void cycleKeyList(Object key) {    // 在 keyList隊列 中注冊要添加的 key    keyList.addLast(key);    // 如果注冊這個 key 會超出容積上限,則把最老的一個緩存項清除掉    if (keyList.size() > size) {      Object oldestKey = keyList.removeFirst();      delegate.removeObject(oldestKey);    }  }  @Override  public Object getObject(Object key) {    return delegate.getObject(key);  }  @Override  public Object removeObject(Object key) {    return delegate.removeObject(key);  }  // 除了清理緩存項,還要清理 key 的注冊列表  @Override  public void clear() {    delegate.clear();    keyList.clear();  }}

LruCache 是按照"近期最少使用算法"(Least Recently Used, LRU)進行緩存清理的裝飾器,在需要清理緩存時,它會清除最近最少使用的緩存項。

public class LruCache implements Cache {  // 被裝飾者  private final Cache delegate;  // 這里使用的是 LinkedHashMap,它繼承了 HashMap,但它的元素是有序的  private Map<Object, Object> keyMap;  // 最近最少被使用的緩存項的 key  private Object eldestKey;  // 國際慣例,構造方法中進行屬性初始化  public LruCache(Cache delegate) {    this.delegate = delegate;    // 這里初始化了 keyMap,并定義了 eldestKey 的取值規則    setSize(1024);  }  public void setSize(final int size) {    // 初始化 keyMap,同時指定該 Map 的初始容積及加載因子,第三個參數true 表示 該LinkedHashMap    // 記錄的順序是 accessOrder,即,LinkedHashMap.get()方法 會改變其中元素的順序    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {      private static final long serialVersionUID = 4267176411845948333L;      // 當調用 LinkedHashMap.put()方法 時,該方法會被調用      @Override      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {        boolean tooBig = size() > size;        if (tooBig) {          // 當已達到緩存上限,更新 eldestKey字段,后面將其刪除          eldestKey = eldest.getKey();        }        return tooBig;      }    };  }  // 存儲緩存項  @Override  public void putObject(Object key, Object value) {    delegate.putObject(key, value);    // 記錄緩存項的 key,超出容量則清除最久未使用的緩存項    cycleKeyList(key);  }  private void cycleKeyList(Object key) {    keyMap.put(key, key);    // eldestKey 不為空,則表示已經達到緩存上限    if (eldestKey != null) {      // 清除最久未使用的緩存      delegate.removeObject(eldestKey);      // 制空      eldestKey = null;    }  }  @Override  public Object getObject(Object key) {    // 訪問 key元素 會改變該元素在 LinkedHashMap 中的順序    keyMap.get(key); //touch    return delegate.getObject(key);  }  @Override  public String getId() {    return delegate.getId();  }  @Override  public int getSize() {    return delegate.getSize();  }  @Override  public Object removeObject(Object key) {    return delegate.removeObject(key);  }  @Override  public void clear() {    delegate.clear();    keyMap.clear();  }}

1.4 SoftCache 和 WeakCache

在分析 SoftCache 和 WeakCache 實現之前,我們再溫習一下 Java 提供的 4 種引用類型,強引用 StrongReference、軟引用 SoftReference、弱引用 WeakReference 和虛引用 PhantomReference。

?強引用 平時用的最多的,如 Object obj = new Object(),新建的 Object 對象 就是被強引用的。如果一個對象被強引用,即使是 JVM 內存空間不足,要拋出 OutOfMemoryError 異常,GC 也絕不會回收該對象。?軟引用 僅次于強引用的一種引用,它使用類 SoftReference 來表示。當 JVM 內存不足時,GC 會回收那些只被軟引用指向的對象,從而避免內存溢出。軟引用適合引用那些可以通過其他方式恢復的對象,例如, 數據庫緩存中的對象就可以從數據庫中恢復,所以軟引用可以用來實現緩存,下面要介紹的 SoftCache 就是通過軟引用實現的。
另外,由于在程序使用軟引用之前的某個時刻,其所指向的對象可能己經被 GC 回收掉了,所以通過 Reference.get()方法 來獲取軟引用所指向的對象時,總是要通過檢查該方法返回值是否為 null,來判斷被軟引用的對象是否還存活。?弱引用 弱引用使用 WeakReference 表示,它不會阻止所引用的對象被 GC 回收。在 JVM 進行垃圾回收時,如果指向一個對象的所有引用都是弱引用,那么該對象會被回收。所以,只被弱引用所指向的對象,其生存周期是 兩次 GC 之間 的這段時間,而只被軟引用所指向的對象可以經歷多次 GC,直到出現內存緊張的情況才被回收。?虛引用 最弱的一種引用類型,由類 PhantomReference 表示。虛引用可以用來實現比較精細的內存使用控制,但很少使用。?引用隊列(ReferenceQueue ) 很多場景下,我們的程序需要在一個對象被 GC 時得到通知,引用隊列就是用于收集這些信息的隊列。在創建 SoftReference 對象 時,可以為其關聯一個引用隊列,當 SoftReference 所引用的對象被 GC 時, JVM 就會將該 SoftReference 對象 添加到與之關聯的引用隊列中。當需要檢測這些通知信息時,就可以從引用隊列中獲取這些 SoftReference 對象。不僅是 SoftReference,弱引用和虛引用都可以關聯相應的隊列。

現在來看一下 SoftCache 的具體實現。

public class SoftCache implements Cache {  // 這里使用了 LinkedList 作為容器,在 SoftCache 中,最近使用的一部分緩存項不會被 GC  // 這是通過將其 value 添加到 hardLinksToAvoidGarbageCollection集合 實現的(即,有強引用指向其value)  private final Deque<Object> hardLinksToAvoidGarbageCollection;  // 引用隊列,用于記錄已經被 GC 的緩存項所對應的 SoftEntry對象  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;  // 持有的被裝飾者  private final Cache delegate;  // 強連接的個數,默認為 256  private int numberOfHardLinks;  // 構造方法進行屬性的初始化  public SoftCache(Cache delegate) {    this.delegate = delegate;    this.numberOfHardLinks = 256;    this.hardLinksToAvoidGarbageCollection = new LinkedList<>();    this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();  }  private static class SoftEntry extends SoftReference<Object> {    private final Object key;    SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {      // 指向 value 的引用是軟引用,并且關聯了 引用隊列      super(value, garbageCollectionQueue);      // 強引用      this.key = key;    }  }  @Override  public void putObject(Object key, Object value) {    // 清除已經被 GC 的緩存項    removeGarbageCollectedItems();    // 添加緩存    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));  }  private void removeGarbageCollectedItems() {    SoftEntry sv;    // 遍歷 queueOfGarbageCollectedEntries集合,清除已經被 GC 的緩存項 value    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {      delegate.removeObject(sv.key);    }  }  @Override  public Object getObject(Object key) {    Object result = null;    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache      // 用一個軟引用指向 key 對應的緩存項      SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);    // 檢測緩存中是否有對應的緩存項    if (softReference != null) {      // 獲取 softReference 引用的 value      result = softReference.get();      // 如果 softReference 引用的對象已經被 GC,則從緩存中清除對應的緩存項      if (result == null) {        delegate.removeObject(key);      } else {        synchronized (hardLinksToAvoidGarbageCollection) {          // 將緩存項的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存          hardLinksToAvoidGarbageCollection.addFirst(result);          // 如果 hardLinksToAvoidGarbageCollection 的容積已經超過 numberOfHardLinks          // 則將最老的緩存項從 hardLinksToAvoidGarbageCollection 中清除,FIFO          if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {            hardLinksToAvoidGarbageCollection.removeLast();          }        }      }    }    return result;  }  @Override  public Object removeObject(Object key) {    // 清除指定的緩存項之前,也會先清理被 GC 的緩存項    removeGarbageCollectedItems();    return delegate.removeObject(key);  }  @Override  public void clear() {    synchronized (hardLinksToAvoidGarbageCollection) {      // 清理強引用集合      hardLinksToAvoidGarbageCollection.clear();    }    // 清理被 GC 的緩存項    removeGarbageCollectedItems();    // 清理最底層的緩存項    delegate.clear();  }  @Override  public String getId() {    return delegate.getId();  }  @Override  public int getSize() {    removeGarbageCollectedItems();    return delegate.getSize();  }  public void setSize(int size) {    this.numberOfHardLinks = size;  }}

WeakCache 的實現與 SoftCache 基本類似,唯一的區別在于其中使用 WeakEntry(繼承了 WeakReference)封裝真正的 value 對象,其他實現完全一樣。

另外,還有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理緩存的裝飾器,它的 clearInterval 字段 記錄了兩次緩存清理之間的時間間隔,默認是一小時,lastClear 字段 記錄了最近一次清理的時間戳。ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在執行時都會根據這兩個字段檢測是否需要進行清理操作,清理操作會清空緩存中所有緩存項。

LoggingCache 在 Cache 的基礎上提供了日志功能,它通過 hit 字段 和 request 字段 記錄了 Cache 的命中次數和訪問次數。在 LoggingCache.getObject()方法 中,會統計命中次數和訪問次數 這兩個指標,井按照指定的日志輸出方式輸出命中率。

SynchronizedCache 通過在每個方法上添加 synchronized 關鍵字,為 Cache 添加了同步功能,有點類似于 JDK 中 Collections 的 SynchronizedCollection 內部類。

SerializedCache 提供了將 value 對象 序列化的功能。SerializedCache 在添加緩存項時,會將 value 對應的 Java 對象 進行序列化,井將序列化后的 byte[]數組 作為 value 存入緩存 。SerializedCache 在獲取緩存項時,會將緩存項中的 byte[]數組 反序列化成 Java 對象。不使用 SerializedCache 裝飾器 進行裝飾的話,每次從緩存中獲取同一 key 對應的對象時,得到的都是同一對象,任意一個線程修改該對象都會影響到其他線程,以及緩存中的對象。而使用 SerializedCache 每次從緩存中獲取數據時,都會通過反序列化得到一個全新的對象。SerializedCache 使用的序列化方式是 Java 原生序列化。

2 CacheKey

在 Cache 中唯一確定一個緩存項,需要使用緩存項的 key 進行比較,MyBatis 中因為涉及 動態 SQL 等多方面因素, 其緩存項的 key 不能僅僅通過一個 String 表示,所以 MyBatis 提供了 CacheKey 類 來表示緩存項的 key,在一個 CacheKey 對象 中可以封裝多個影響緩存項的因素。CacheKey 中可以添加多個對象,由這些對象共同確定兩個 CacheKey 對象 是否相同。

public class CacheKey implements Cloneable, Serializable {  private static final long serialVersionUID = 1146682552656046210L;  public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();  private static final int DEFAULT_MULTIPLYER = 37;  private static final int DEFAULT_HASHCODE = 17;  // 參與計算hashcode,默認值DEFAULT_MULTIPLYER = 37  private final int multiplier;  // 當前CacheKey對象的hashcode,默認值DEFAULT_HASHCODE = 17  private int hashcode;  // 校驗和  private long checksum;  private int count;  // 由該集合中的所有元素 共同決定兩個CacheKey對象是否相同,一般會使用一下四個元素  // MappedStatement的id、查詢結果集的范圍參數(RowBounds的offset和limit)  // SQL語句(其中可能包含占位符"?")、SQL語句中占位符的實際參數  private List<Object> updateList;  // 構造方法初始化屬性  public CacheKey() {    this.hashcode = DEFAULT_HASHCODE;    this.multiplier = DEFAULT_MULTIPLYER;    this.count = 0;    this.updateList = new ArrayList<>();  }  public CacheKey(Object[] objects) {    this();    updateAll(objects);  }  public void update(Object object) {    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);    // 重新計算count、checksum和hashcode的值    count++;    checksum += baseHashCode;    baseHashCode *= count;    hashcode = multiplier * hashcode + baseHashCode;    // 將object添加到updateList集合    updateList.add(object);  }  public int getUpdateCount() {    return updateList.size();  }  public void updateAll(Object[] objects) {    for (Object o : objects) {      update(o);    }  }  /**   * CacheKey重寫了 equals() 和 hashCode()方法,這兩個方法使用上面介紹   * 的 count、checksum、hashcode、updateList 比較兩個 CacheKey對象 是否相同   */  @Override  public boolean equals(Object object) {    // 如果為同一對象,直接返回 true    if (this == object) {      return true;    }    // 如果 object 都不是 CacheKey類型,直接返回 false    if (!(object instanceof CacheKey)) {      return false;    }    // 類型轉換一下    final CacheKey cacheKey = (CacheKey) object;    // 依次比較 hashcode、checksum、count,如果不等,直接返回 false    if (hashcode != cacheKey.hashcode) {      return false;    }    if (checksum != cacheKey.checksum) {      return false;    }    if (count != cacheKey.count) {      return false;    }    // 比較 updateList 中的元素是否相同,不同直接返回 false    for (int i = 0; i < updateList.size(); i++) {      Object thisObject = updateList.get(i);      Object thatObject = cacheKey.updateList.get(i);      if (!ArrayUtil.equals(thisObject, thatObject)) {        return false;      }    }    return true;  }  @Override  public int hashCode() {    return hashcode;  }  @Override  public String toString() {    StringJoiner returnValue = new StringJoiner(":");    returnValue.add(String.valueOf(hashcode));    returnValue.add(String.valueOf(checksum));    updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);    return returnValue.toString();  }  @Override  public CacheKey clone() throws CloneNotSupportedException {    CacheKey clonedCacheKey = (CacheKey) super.clone();    clonedCacheKey.updateList = new ArrayList<>(updateList);    return clonedCacheKey;  }}

感謝各位的閱讀!關于“Mybatis緩存模塊的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

镇赉县| 柏乡县| 九龙县| 潞城市| 黔东| 曲靖市| 安康市| 北海市| 青冈县| 威信县| 涿州市| 晋江市| 白银市| 涟水县| 安化县| 天柱县| 安国市| 富裕县| 辽中县| 象山县| 凌源市| 诏安县| 平顺县| 昌江| 拉萨市| 钟山县| 汉中市| 凭祥市| 上栗县| 彩票| 昂仁县| 万载县| 枣强县| 两当县| 芦溪县| 固安县| 慈利县| 怀安县| 光泽县| 化州市| 曲阳县|