Mybatis緩存策略源碼

Mybatis3.5.1源碼分析

  1. Mybatis-SqlSessionFactoryBuilder,XMLConfigBuilder,XPathParser源碼解析
  2. Mybatis-Configuration源碼解析
  3. Mybatis-事務(wù)對象源碼解析
  4. Mybatis-數(shù)據(jù)源源碼解析
  5. Mybatis緩存策略源碼解析
  6. Mybatis-DatabaseIdProvider源碼解析
  7. Mybatis-TypeHandler源碼解析
  8. Mybatis-Reflector源碼解析
  9. Mybatis-ObjectFactory,ObjectWrapperFactory源碼分析
  10. Mybatis-Mapper各類標(biāo)簽封裝類源碼解析
  11. Mybatis-XMLMapperBuilder,XMLStatmentBuilder源碼分析
  12. Mybatis-MapperAnnotationBuilder源碼分析
  13. [Mybatis-MetaObject,MetaClass源碼解析]http://www.itdecent.cn/p/f51fa552f30a)
  14. Mybatis-LanguageDriver源碼解析
  15. Mybatis-SqlSource源碼解析
  16. Mybatis-SqlNode源碼解析
  17. Mybatis-KeyGenerator源碼解析
  18. Mybatis-Executor源碼解析
  19. Mybatis-ParameterHandler源碼解析
  20. Mybatis-StatementHandler源碼解析
  21. Mybatis-DefaultResultSetHandler(一)源碼解析
  22. Mybatis-DefaultResultSetHandler(二)源碼解析
  23. Mybatis-ResultHandler,Cursor,RowBounds 源碼分析
  24. Mybatis-MapperProxy源碼解析
  25. Mybatis-SqlSession源碼解析
  26. Mybatis-Interceptor源碼解析

Cache

緩存策略類都是基于裝飾者設(shè)計模式進(jìn)行開發(fā)的

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

/**
 * SPI for cache providers. 為緩存提供者的接口(SPI:串行外設(shè)接口。)
 * <p>
 * One instance of cache will be created for each namespace. 每個命名空間都會創(chuàng)建一個緩存的實(shí)例對象
 * <p>
 * The cache implementation must have a constructor that receives the cache id as an String parameter.
 * 繼承{@link Cache}的實(shí)現(xiàn)類必須有一個可接收一個緩存ID的String類型參數(shù)的構(gòu)造函數(shù)
 * <p>
 * MyBatis will pass the namespace as id to the constructor.
 * Mybatis會將命名空間作為id傳入構(gòu)造方法
 *
 * <pre>
 * public MyCache(final String id) {
 *  if (id == null) {
 *    throw new IllegalArgumentException("Cache instances require an ID");
 *  }
 *  this.id = id;
 *  initialize();
 * }
 * </pre>
 * @author Clinton Begin
 */

public interface Cache {

  /**
   * 獲取緩存對象的唯一標(biāo)識,一般是Mapper.xml的命名空間
   * @return The identifier of this cache
   */
  String getId();

  /**
   * 保存key/value到緩存對象中
   * @param key Can be any object but usually it is a {@link CacheKey} 可以是任何對象,但一般是CacheKey對象
   * @param value The result of a select. 查詢結(jié)果
   */
  void putObject(Object key, Object value);

  /**
   * 從緩存對象中獲取key對應(yīng)的value
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   * <p>
   *     移除key對應(yīng)的value
   * </p>
   * As of 3.3.0 this method is only called during a rollback
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be
   * available instead of hitting the database.
   * 在3.3.0后,這個方法只是在回滾在緩存中丟失了的任何之前的值。
   * 這使得任何一個阻塞緩存釋放可能之前寫入鍵的鎖。
   * 一個阻塞緩存當(dāng)值為null時加一個鎖,在值再次后臺時釋放這個所。
   * 這樣其他線程會等待可用的價值,而不是數(shù)據(jù)庫。
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance.
   * 清空緩存
   */
  void clear();

  /**
   * Optional. This method is not called by the core.
   * 獲取緩存對象中存儲的鍵/值對的數(shù)量
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize();

  /**
   * 獲取讀寫鎖,這個方法mybatis的所有Cache實(shí)現(xiàn)類都沒有重寫過,都是直接返回null
   * <p>
   *   從3.2.6起這個方法不再被框架核心調(diào)用,任何需要的鎖,都必須由緩存供應(yīng)商提供
   * </p>
   * Optional. As of 3.2.6 this method is no longer called by the core.
   * <p>
   * Any locking needed by the cache must be provided internally by the cache provider.
   *
   * @return A ReadWriteLock
   */
  ReadWriteLock getReadWriteLock();

}

PerpetualCache

/**
 *    Copyright 2009-2018 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * 永久緩存 Cache接口實(shí)現(xiàn)類,里面就是維護(hù)著一個HashMap
 * <p>
 *     Cache接口只有這唯一一個基礎(chǔ)實(shí)現(xiàn),其他實(shí)現(xiàn)類全都是裝飾模式持有另一個緩存對象
 * </p>
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  /**
   * 緩存對象的唯一標(biāo)識
   */
  private final String id;

  /**
   * 對象內(nèi)部維護(hù)的HashMap,緩存Map
   */
  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();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  /**
   * 通過判斷ID去實(shí)現(xiàn)相等,如果當(dāng)前類型對象的ID屬性為null會拋出{@link CacheException}
   */
  @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;
    //判斷ID
    return getId().equals(otherCache.getId());
  }

  /**
   * 以為屬性ID的hashCode作為本類對象的hashCode,如果當(dāng)前類型對象的ID屬性為null會拋出{@link CacheException}
   */
  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

FifoCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * FIFO (first in, first out) cache decorator.
 * 使用先進(jìn)先出緩存策略的緩存裝飾類
 * @author Clinton Begin
 */
public class FifoCache implements Cache {

  /**
   * 委托Cache對象
   */
  private final Cache delegate;
  /**
   * 鍵列表
   * <p>
   *     Deque是Queue的子接口,我們知道Queue是一種隊(duì)列形式,而Deque則是雙向隊(duì)列,它支持從兩個端點(diǎn)方向檢索和插入元素,
   *     因此Deque既可以支持LIFO形式也可以支持LIFO形式.Deque接口是一種比Stack和Vector更為豐富的抽象數(shù)據(jù)形式,因?yàn)樗瑫r實(shí)現(xiàn)了以上兩者.
   * </p>
   * <p>
   *     初始化時,該屬性的實(shí)現(xiàn)類默認(rèn)是{@link LinkedList}
   * </p>
   */
  private final Deque<Object> keyList;

  /**
   * 最大緩存數(shù),初始化時為1024
   */
  private int size;

  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<>();
    this.size = 1024;
  }

  /**
   * 取{@link #delegate}的ID
   */
  @Override
  public String getId() {
    return delegate.getId();
  }

  /**
   * 取{@link #delegate}的當(dāng)前緩存數(shù)
   */
  @Override
  public int getSize() {
    return delegate.getSize();
  }

  /**
   * 設(shè)置最大緩存數(shù)
   * @param size
   */
  public void setSize(int size) {
    this.size = size;
  }

  /**
   * 添加數(shù)據(jù)進(jìn)緩存
   * @param key Can be any object but usually it is a {@link @CacheKey} 可以是任何對象,但一般是CacheKey對象
   * @param value The result of a select. 查詢結(jié)果
   */
  @Override
  public void putObject(Object key, Object value) {
    cycleKeyList(key);
    delegate.putObject(key, value);
  }

  /**
   * 從{@link #delegate}中獲取{@link @key}對應(yīng)的緩存數(shù)據(jù)
   */
  @Override
  public Object getObject(Object key) {
    return delegate.getObject(key);
  }

  /**
   * 從{@link #delegate}中刪除{@link @key}對應(yīng)的緩存數(shù)據(jù)
   */
  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  /**
   * 清空緩存,{@link #delegate},{@link #keyList}都會清空
   */
  @Override
  public void clear() {
    delegate.clear();
    keyList.clear();
  }

  /**
   * 獲取讀寫鎖,該類未實(shí)現(xiàn)讀寫鎖,直接返回null
   * @return
   */
  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  /**
   * 周期檢查健名列表
   * <p>
   *     將{@link @key}記錄到{@link #keyList}中,再判斷{@link #keyList}大小是否超過設(shè)定的最大值,超過就取出{@link #keyList}的
   *     第一個元素賦值給{@link @oldestKey},然后刪掉{@link #delegate}的{@link @oldestKey}緩存
   * </p>
   * @param key
   */
  private void cycleKeyList(Object key) {
    keyList.addLast(key);
    //檢查當(dāng)前keyList的大小是否大于設(shè)定的最大值
    if (keyList.size() > size) {
      Object oldestKey = keyList.removeFirst();//移除列表中的第一個元素,并返回
      delegate.removeObject(oldestKey);//移除對應(yīng)的緩存
    }
  }

}

LruCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * Lru (least recently used) cache decorator.
 * 使用最近最少使用的緩存策略的緩存裝飾類
 * @author Clinton Begin
 */
public class LruCache implements Cache {

  /**
   * 真正存放緩存數(shù)據(jù)的Cache對象,委托類
   */
  private final Cache delegate;
  /**
   * 記錄著每個key的訪問次數(shù)
   */
  private Map<Object, Object> keyMap;
  /**
   * 最近最少用的元素的key
   */
  private Object eldestKey;

  /**
   * 設(shè)置委托的Cache對象,并初始化最大緩存數(shù),默認(rèn)是1024
   * @param delegate
   */
  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }

  /**
   * 獲取{@link #delegate}的ID
   * @return
   */
  @Override
  public String getId() {
    return delegate.getId();
  }

  /**
   * 獲取{@link #delegate}的緩存數(shù)
   * @return
   */
  @Override
  public int getSize() {
    return delegate.getSize();
  }

  /**
   * 設(shè)置最大緩存數(shù)
   * <p>
   *     每次調(diào)用該方法,都會重新新建一個LinkeHashMap實(shí)例對賦值給{@link #keyMap},這意思之前的keyMap所記錄的每個元素的訪問次數(shù)
   *     都會丟失,重新記錄訪問次數(shù)。而{@link #eldestKey}并不會置空,還是會在每次添加緩存數(shù)據(jù)后,刪除對應(yīng){@link #eldestKey}的
   *     {@link #delegate}里的元素。之所以這樣設(shè)計,推測可能是因?yàn)樵O(shè)計者任務(wù)重新記錄每個元素的訪問次數(shù)并不會造成太大的業(yè)務(wù)問題,并且
   *     既然重新調(diào)用了該方法,之前的擴(kuò)容算法和初始化容量大小都應(yīng)該都應(yīng)該按照新的{@link @size}來重新設(shè)置,以保證性能的最佳
   * </p>
   * @param size
   */
  public void setSize(final int size) {
    /**
     * LinkedHashMap構(gòu)造函數(shù):
     * 第一個參數(shù)initialCapacity:初始化容量大小
     * 第2個參數(shù)loadFactor:后面如果LinkedHashMap需要增大長度,按照capacity*loadFactor取整后增長
     * 第3個參數(shù)accessOrder:
     * accessOrder設(shè)置為false,表示不是訪問順序而是插入順序存儲的,這也是默認(rèn)值,表示LinkedHashMap中存儲的順序是按照調(diào)用put方法插入的順序進(jìn)行排序的
     */
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      /**
       * <p>
       * 該方法是LinkedHashMap提供的一個鉤子方法,是交給用戶自己根據(jù)業(yè)務(wù)實(shí)現(xiàn)的。該方法會在添加
       *  完元素后被調(diào)用,如果返回的是false,就不會刪除最近最少使用的元素。默認(rèn)是返回false。
       * </p>
       * <p>
       *   如果大于{@link LruCache#keyMap}的大小大于設(shè)定的{@link #size},
       *    就會返回true,并將最近最少使用的元素的key賦值給{@link LruCache#eldestKey}
       * </p>
       * @param eldest
       * @return
       */
      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();//記錄最近最少使用的元素
        }
        return tooBig;
      }
    };
  }

  /**
   * 添加緩存數(shù)據(jù)
   * <p>
   *     除了添加緩存數(shù)據(jù)到{@link #delegate}以外,還將{@link @key}加入到{@link #keyMap}中進(jìn)行記錄,
   *      并將將{@link #delegate}里最近最少用的元素刪除。
   * </p>
   * @param key Can be any object but usually it is a {@link @CacheKey} 可以是任何對象,但一般是CacheKey對象
   * @param value The result of a select. 查詢結(jié)果
   */
  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    // 將{@link @key}加入到{@link #keyMap}中進(jìn)行記錄,并將將{@link #delegate}里最近最少用的元素刪除。
    cycleKeyList(key);
  }

  /**
   * 獲取緩存數(shù)據(jù)
   * <p>
   *     除了從{@link #delegate}中取出對應(yīng){@link @key}的緩存數(shù)據(jù),{@link @keyMap}也會對{@link @key}記錄訪問次數(shù)。
   * </p>
   */
  @Override
  public Object getObject(Object key) {
    keyMap.get(key); //touch 這里會使得key在keyMap里的訪問次數(shù)加1
    return delegate.getObject(key);
  }

  /**
   * 刪除{@link #delegate}的對應(yīng)的{@link @key}的緩存數(shù)據(jù)
   */
  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  /**
   * 清空{(diào)@link #delegate}和{@link #keyMap}的所有數(shù)據(jù)
   */
  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  /**
   * 獲取讀寫鎖,該類未實(shí)現(xiàn)讀寫鎖,直接返回null
   */
  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  /**
   * 周期檢查健名列表
   * <p>
   *     將{@link @key}加入到{@link #keyMap}中進(jìn)行記錄,并將將{@link #delegate}里最近最少用的元素刪除。
   * </p>
   * @param key
   */
  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    //將{@link #delegate}里最近最少用的元素刪除。
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

SoftCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * Soft Reference cache decorator
 * Thanks to Dr. Heinz Kabutz for his guidance here.
 * 軟引用回收策略 緩存裝飾類
 * <p>
 *     軟引用只有當(dāng)內(nèi)存不足時才會被垃圾收集器回收。這里的實(shí)現(xiàn)機(jī)制中,使用了一個鏈表來保證一定數(shù)量的值即使內(nèi)存不足也不會被回收,
 *     但是沒有保存在該鏈表的值則有可能會被回收
 * </p>
 * @author Clinton Begin
 */
public class SoftCache implements Cache {

  /**
   * 硬列表,以避免GC,初始化對象實(shí)例為LinkedList
   * <p>用于保存一定數(shù)量強(qiáng)引用的值</p>
   */
  private final Deque<Object> hardLinksToAvoidGarbageCollection;

  /**
   *    垃圾收集條目的隊(duì)列
   *  <p>引用隊(duì)列,當(dāng)被垃圾收集器回收時,會將軟引用對象放入此隊(duì)列</p>
   *  <p>
   *      當(dāng)GC回收時,會將對象
   *  </p>
   */
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;

  /**
   * 真正的緩存類,委托Cache對象
   */
  private final Cache delegate;

  /**
   * 保存強(qiáng)引用值的數(shù)量,初始化為256
   */
  private int numberOfHardLinks;

  public SoftCache(Cache delegate) {
    this.delegate = delegate;
    this.numberOfHardLinks = 256;
    this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
    this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
  }

  /**
   * 取{@link #delegate}的ID
   */
  @Override
  public String getId() {
    return delegate.getId();
  }

  /**
   * 獲取取{@link #delegate}的當(dāng)前緩存數(shù)
   */
  @Override
  public int getSize() {
    removeGarbageCollectedItems();
    return delegate.getSize();
  }


  public void setSize(int size) {
    this.numberOfHardLinks = size;
  }

  @Override
  public void putObject(Object key, Object value) {
    // 移除被垃圾收集器回收的鍵值
    removeGarbageCollectedItems();
    // 將軟引用作用到Value中
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  }

  /**
   * 獲取緩存數(shù)據(jù),獲取的數(shù)據(jù)如果不為null,會將這個數(shù)據(jù)放入強(qiáng)引用隊(duì)列中,隊(duì)列會判斷當(dāng)前可容納的數(shù)量,超過了就采用先進(jìn)先出的策略進(jìn)行移除。
   * @param key The key
   * @return 有可能返回null。因?yàn)镚C可能已經(jīng)清理相關(guān)數(shù)據(jù)
   */
  @Override
  public Object getObject(Object key) {
    Object result = null;
    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
    SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
    if (softReference != null) {
      result = softReference.get();
      if (result == null) {
        // 該值被垃圾收集器回收,移除掉該項(xiàng)
        delegate.removeObject(key);
      } else {
        /**
         * 這里以及下面的clear,想不通為什么要加hardLinksToAvoidGarbageCollection的同步?(在WeakCache中卻沒有加同步)
         *    --我的推測:因?yàn)镚C是一條線程,同步能使得主線程先執(zhí)行下面代碼后,GC才會工作,這樣才能防止GC在主線程執(zhí)行下面代碼時
         *      回收了對象。
         */
        // See #586 (and #335) modifications need more than a read lock
        synchronized (hardLinksToAvoidGarbageCollection) {
          //存入經(jīng)常訪問的鍵值到鏈表(最多256元素),防止垃圾回收
          hardLinksToAvoidGarbageCollection.addFirst(result);
          if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
            // 超出容量,則移除最先保存的引用
            //因?yàn)榧尤胧羌尤氲搅斜淼牡谝粋€位置,隨意最后一個就是最先加入的元素。
            hardLinksToAvoidGarbageCollection.removeLast();
          }
        }
      }
    }
    return result;
  }


  /**
   * 刪除對應(yīng){@link @key}的緩存數(shù)據(jù)
   * @param key The key
   * @return
   */
  @Override
  public Object removeObject(Object key) {
    // 移除被垃圾收集器回收的鍵值
    removeGarbageCollectedItems();
    return delegate.removeObject(key);
  }

  /**
   * 清空緩存數(shù)據(jù)
   * <p>
   *     清空強(qiáng)引用,移除被垃圾收集器回收的鍵值,清空緩存數(shù)據(jù)
   * </p>
   */
  @Override
  public void clear() {
    //清空強(qiáng)引用
    synchronized (hardLinksToAvoidGarbageCollection) {
      hardLinksToAvoidGarbageCollection.clear();
    }
    // 移除被垃圾收集器回收的鍵值
    removeGarbageCollectedItems();
    //清空緩存數(shù)據(jù)
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  /**
   * 刪除被垃圾收集器回收的鍵值
   */
  private void removeGarbageCollectedItems() {
    SoftEntry sv;
    // 清空被垃圾收集器回收的value其相關(guān)聯(lián)的鍵以及軟引用
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      delegate.removeObject(sv.key);
    }
  }

  /**
   * 繼承了{(lán)@link SoftReference},使得傳進(jìn)來的value轉(zhuǎn)變成軟引用。
   *<p>
   *     這里將其Value作為軟引用,而不是用key,因?yàn)镵ey不能被回收,如果被移除的話,就會影響到整個體系,
   *     最底層的實(shí)現(xiàn)使用HashMap實(shí)現(xiàn)的,沒有Key,就沒有辦法移除相關(guān)的值。反過來,值被回收了,將軟引用對象放到隊(duì)列中,
   *     可以根據(jù)Key調(diào)用removeObject移除該關(guān)聯(lián)的鍵和軟引用對象。
   *</p>
   */
  private static class SoftEntry extends SoftReference<Object> {
    /**
     *  保存與value相關(guān)聯(lián)的Key,因?yàn)橐坏┍焕占骰厥眨瑒t此軟引用對象會被放到關(guān)聯(lián)的引用隊(duì)列中,
     *  這樣就可以根據(jù)Key,移除該鍵值對
     */
    private final Object key;

    SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
      super(value, garbageCollectionQueue);
      this.key = key;
    }
  }

}

WeakCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * Weak Reference cache decorator.
 * Thanks to Dr. Heinz Kabutz for his guidance here.
 * <p>
 *     弱引用回收策略 緩存裝飾類,邏輯和{@link SoftCache}非常類似。就直接看{@link SoftCache}邏輯算了,我懶得寫了
 * </p>
 * <p>
 *     弱引用的對象一旦被垃圾收集器發(fā)現(xiàn),則會被回收,無論內(nèi)存是否足夠
 * </p>
 * @author Clinton Begin
 */
public class WeakCache implements Cache {
  /**
   *
   */
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  private final Cache delegate;
  private int numberOfHardLinks;

  public WeakCache(Cache delegate) {
    this.delegate = delegate;
    this.numberOfHardLinks = 256;
    this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
    this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    removeGarbageCollectedItems();
    return delegate.getSize();
  }

  public void setSize(int size) {
    this.numberOfHardLinks = size;
  }

  @Override
  public void putObject(Object key, Object value) {
    removeGarbageCollectedItems();
    delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
  }

  @Override
  public Object getObject(Object key) {
    Object result = null;
    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
    WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
    if (weakReference != null) {
      result = weakReference.get();
      if (result == null) {
        delegate.removeObject(key);
      } else {
        hardLinksToAvoidGarbageCollection.addFirst(result);
        if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
          hardLinksToAvoidGarbageCollection.removeLast();
        }
      }
    }
    return result;
  }

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

  @Override
  public void clear() {
    hardLinksToAvoidGarbageCollection.clear();
    removeGarbageCollectedItems();
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  private void removeGarbageCollectedItems() {
    WeakEntry sv;
    while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      delegate.removeObject(sv.key);
    }
  }

  private static class WeakEntry extends WeakReference<Object> {
    private final Object key;

    private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
      super(value, garbageCollectionQueue);
      this.key = key;
    }
  }

}

LoggingCache

/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

/**
 * 日志功能,裝飾類,用于記錄緩存的命中率,如果開啟了DEBUG模式,則會輸出命中率日志。
 * <p>
 *     參考博客:<a >https://www.cnblogs.com/jabnih/p/5705640.html</a>
 * </p>
 * @author Clinton Begin
 */
public class LoggingCache implements Cache {

  /**
   * log的名字一般是命名空間,從{@link #delegate}的{@link Cache#getId()}方法獲取。
   */
  private final Log log;
  private final Cache delegate;
  /**
   * 請求次數(shù)
   */
  protected int requests = 0;
  /**
   * 命中次數(shù)
   */
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  /**
   *  獲取緩存數(shù)據(jù),并統(tǒng)計請求次數(shù)和命中次數(shù)
   * @param key The key
   * @return
   */
  @Override
  public Object getObject(Object key) {
    requests++;//每次獲取,請求次數(shù)+1
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;//獲取的緩存數(shù)據(jù)時存在的,命中次數(shù)+1
    }
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

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

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  /**
   * 獲取命中率
   * <p>
   *     命中率 = 命中次數(shù) / 請求次數(shù)
   * </p>
   * @return
   */
  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

}

ScheduledCache

/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * 定時清空Cache,但是并沒有開始一個定時任務(wù),而是在使用Cache的時候,才去檢查時間是否到了。
 * <p>
 *     參考博客:<a >https://www.cnblogs.com/jabnih/p/5705640.html</a>
 * </p>
 * @author Clinton Begin
 */
public class ScheduledCache implements Cache {

  private final Cache delegate;
  /**
   * 清除緩存數(shù)據(jù)的時間間隔,初始化為1小時
   */
  protected long clearInterval;
  /**
   * 上一次清除的時間,初始化時賦值當(dāng)前時間
   */
  protected long lastClear;

  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    this.clearInterval = 60 * 60 * 1000; // 1 hour
    this.lastClear = System.currentTimeMillis();
  }

  public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    clearWhenStale();
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    clearWhenStale();
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    return clearWhenStale() ? null : delegate.getObject(key);
  }

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

  /**
   *  清空所有緩存數(shù)據(jù)
   */
  @Override
  public void clear() {
    lastClear = System.currentTimeMillis();//記錄當(dāng)前時間
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  /**
   * 判斷是否到了清空數(shù)據(jù)的時間
   */
  private boolean clearWhenStale() {
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

}

SerializedCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
import org.apache.ibatis.io.Resources;

/**
 * 序列化功能,將值序列化后存到緩存中。該功能用于緩存返回一份實(shí)例的Copy,用于保存線程安全。
 * <p>
 *     該緩存只是保存在內(nèi)存里
 * </p>
 * <p>
 *     參考博客:<a >https://www.cnblogs.com/jabnih/p/5705640.html</a>
 * </p>
 * @author Clinton Begin
 */
public class SerializedCache implements Cache {

  private final Cache delegate;

  public SerializedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      // 先序列化后再存放到緩存中
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    // 不為空,則反序列化,生成一份Copy
    return object == null ? null : deserialize((byte[]) object);
  }

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

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  /**
   * 序列化
   */
  private byte[] serialize(Serializable value) {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(bos)) {
      oos.writeObject(value);
      oos.flush();
      return bos.toByteArray();
    } catch (Exception e) {
      throw new CacheException("Error serializing object.  Cause: " + e, e);
    }
  }

  // 反序列化
  private Serializable deserialize(byte[] value) {
    Serializable result;
    try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
         ObjectInputStream ois = new CustomObjectInputStream(bis)) {
      result = (Serializable) ois.readObject();
    } catch (Exception e) {
      throw new CacheException("Error deserializing object.  Cause: " + e, e);
    }
    return result;
  }

  public static class CustomObjectInputStream extends ObjectInputStream {

    public CustomObjectInputStream(InputStream in) throws IOException {
      super(in);
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
      // 此方法只有在待序列化的類第一次序列化的時候才會被調(diào)用
      // 遍歷所支持的ClassLoader,加載對應(yīng)的Class
      return Resources.classForName(desc.getName());
    }

  }

}

SynchronizedCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * 同步Cache,實(shí)現(xiàn)比較簡單,直接使用synchronized修飾方法。
 * @author Clinton Begin
 */
public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

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

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

}

TransactionalCache

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

/**
 * The 2nd level cache transactional buffer.2級緩存事務(wù)緩沖區(qū)
 * <p>
 * This class holds all cache entries that are to be added to the 2nd level cache during a Session.
 * Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
 * Blocking cache support has been added. Therefore any get() that returns a cache miss
 * will be followed by a put() so any lock associated with the key can be released.
 * 這個類保留著所有在對話期間被添加到二級緩存的緩存數(shù)據(jù)。緩存數(shù)據(jù)在事務(wù)提交后會被傳入二級緩存中,或者在事務(wù)會話回退后被丟棄。
 * 阻塞著已經(jīng)添加緩存的緩存數(shù)據(jù),因此任何get()方法返回出來的緩存丟失,會隨后將由put(),所以任何的和對應(yīng)的key關(guān)聯(lián)的鎖可以被釋放掉。
 * <p>
 *     事務(wù)緩存,在提交的時候,才真正的放到Cache中,或者回滾的時候清除掉,對Cache沒有影響
 * </p>
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  /**
   * 真正存放緩存數(shù)據(jù)的Cache對象,委托類
   */
  private final Cache delegate;

  /**
   * 是否在commit時清除二級緩存的標(biāo)記
   */
  private boolean clearOnCommit;

  /**
   *    需要在commit時提交到二級緩存的數(shù)據(jù)
   */
  private final Map<Object, Object> entriesToAddOnCommit;

  /**
   * 緩存未命中的數(shù)據(jù),事務(wù)commit時,也會放入二級緩存(key,null)
   */
  private final Set<Object> entriesMissedInCache;

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

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  /**
   * 獲取緩存數(shù)據(jù)
   * <p>
   *     首先會在對應(yīng)的二級緩存對象中查詢,并將未命中的key記錄到entiresMissedInCache中,之后根據(jù)clearOnCommit決定返回值
   * </p>
   * @param key The key
   * @return
   */
  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);//記錄未命中的key
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  /**
   * 將沒有提及的數(shù)據(jù)記錄下來
   * @param key Can be any object but usually it is a {@link @CacheKey} 可以是任何對象,但一般是CacheKey對象
   * @param object
   */
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  /**
   * 清空entriesToAddOnCommit,并且設(shè)置clearOnCommit為true
   */
  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      // 清空二級緩存
      delegate.clear();
    }
    //將數(shù)據(jù)刷新到二級緩存
    flushPendingEntries();
    //重置clearOnCommit為false,清空entriesToAddOnCommit、entriesMissedInCache集合
    reset();
  }

  /**
   * 回退
   * <p>
   *     根據(jù){@link #entriesMissedInCache},刪除二級緩存的數(shù)據(jù),重置clearOnCommit為false,
   *      清空entriesToAddOnCommit、entriesMissedInCache集合
   * </p>
   */
  public void rollback() {
    //解鎖丟失的緩存數(shù)據(jù),使其根據(jù){@link #entriesMissedInCache}刪除二級緩存的數(shù)據(jù)
    unlockMissedEntries();
    //重置clearOnCommit為false,清空entriesToAddOnCommit、entriesMissedInCache集合
    reset();
  }

  /**
   * 重置一級緩存
   * <p>
   *    {@link #entriesToAddOnCommit}和{@link #entriesMissedInCache}全清空,而不清空二級緩存。
   *        并將{@link #clearOnCommit}標(biāo)記設(shè)置為false
   * </p>
   *
   */
  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  /**
   * 將數(shù)據(jù)刷新到二級緩存
   * <p>
   *     將entriesToAddOnCommit和entriesMissedInCache添加到二級緩存,entriesMissedInCache只存儲了key,所以添加到二級緩存時,value為null
   * </p>
   */
  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)) {//entriesMissedInCache只存儲了key,所以添加到二級緩存時,value為null
        delegate.putObject(entry, null);
      }
    }
  }

  /**
   * 解鎖丟失的緩存數(shù)據(jù)
   * <p>
   *     刪除在二級緩存中關(guān)于{@link #entriesMissedInCache}的所有元素的緩存數(shù)據(jù),這里捕捉了所有移除緩存時的異常,保證每個緩存都能刪除
   * </p>
   */
  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      //將try..catch放在循環(huán)里面,能夠使得每個緩存數(shù)據(jù)都會執(zhí)行刪除,而不會導(dǎo)致有一個刪除數(shù)據(jù)因?yàn)閽伋霎惓?,而?dǎo)致其他緩存數(shù)據(jù)沒有刪除
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容