Mybatis源码阅读----缓存模块(Cache)

Mybatis支持一级缓存和二级缓存,以及缓存默认是开启的,而二建缓存需要进行配置。这两两级缓存都依赖cache模块。
Cache模块所在的位置:
在这里插入图片描述
Cache模块主要用了装饰者设计模式,增强类中的方法,Cache的实现类只有一个PerpetualCache,其余皆为装饰者类。
1、Cache接口如下

public interface Cache {

  /**
   *  缓存标识
   * @return The identifier of this cache
   */
  String getId();

  /**
   *  添加指定键的值,类似于map的put() 方法
   */
  void putObject(Object key, Object value);

  /**
   *  根据key获取缓存
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   *  移除指定的缓存
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

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

  /**
   *  获取容器中缓存的数量
   */
  int getSize();

  /**
   * 默认方法
   * @return A ReadWriteLock
   */
  default ReadWriteLock getReadWriteLock() {
    return null;
  }

}

其实现类PerpetualCache类如下

public class PerpetualCache implements Cache {

  /**
   *  缓存的唯一标识
   */
  private final String id;

  /**
   *  存储缓存的内容
   */
  private final Map<Object, Object> cache = new HashMap<>();

  /**
   *  构造器
   * @param id
   */
  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 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();
  }

}

比较简单。
2、在decorators包中,全部都是装饰者类
1)、BlockingCache保证在某一时刻只有一个线程获取缓存中的数据,如下

**
   *  超时时间
   */
  private long timeout;
  /**
   *  装饰的cache
   */
  private final Cache delegate;

  /**
   *  为指定key上锁的容器,即每一个key对应一个锁
   */
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

主要的实现方法如下

 @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      /**
       *  释放当前线程的锁
       */
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    // 获取锁
    acquireLock(key);
    Object value = delegate.getObject(key);
    if (value != null) {
      // 当获取的缓存值存在时,则释放锁,否则锁将会继续被该线程持有
      releaseLock(key);
    }
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  }


  private ReentrantLock getLockForKey(Object key) {
    // 将指定key上锁并存放在concurrentHashMap中
    return locks.computeIfAbsent(key, k -> new ReentrantLock());
  }

  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 void releaseLock(Object key) {
    // 根据key获取锁
    ReentrantLock lock = locks.get(key);
    // 判断当前线程是否持有该锁
    if (lock.isHeldByCurrentThread()) {
      //持有则释放
      lock.unlock();
    }
  }

2)、LoggingCache支持打印日志的实现类,其定义属性如下

/***
   *  mybatis 的 log 对象
   */
  private final Log log;
  // 装饰者者设计模式,对cache实现类进一步进行增强
  private final Cache delegate;

  /**
   *  请求获取缓存的次数
   */
  protected int requests = 0;

  /**
   *  统计缓存命中次数
   */
  protected int hits = 0;

其主要增强的方法为getObject(key),如下:


  @Override
  public Object getObject(Object key) {
    // 获取缓存次数加1
    requests++;
    // 获取缓存内容
    final Object value = delegate.getObject(key);
    if (value != null) {
      // 内容不为空, 命中 +1
      hits++;
    }
    if (log.isDebugEnabled()) {
      // 打印出缓存命中率
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    // 返回缓存内容
    return value;
  }

getHitRatio()方法用于计算命中率,如下

 /**
   *  命中率 = 命中数 / 总获取数
   * @return
   */
  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

3)、ScheduledCache主要是周期性清除缓存,比较简单,如下

 /**
   *  定时时间  ms
   */
  protected long clearInterval;

  /**
   * 上次清空时间  ms
   */
  protected long lastClear;

  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    // 默认1小时
    this.clearInterval = TimeUnit.HOURS.toMillis(1);
    // 开始时,上次清空时间为当前系统时间
    this.lastClear = System.currentTimeMillis();
  }
@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) {
    // 若时间到了清空缓存,返回null,否则根据key进行获取
    return clearWhenStale() ? null : delegate.getObject(key);
  }

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

  @Override
  public void clear() {
    lastClear = System.currentTimeMillis();
    delegate.clear();
  }

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

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

  private boolean clearWhenStale() {
    // 判断当前时间减去上次清空时间是否大于定时时间,若大于,说明过期,清空
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

4)、SynchronizedCache主要是对cache实现类的方法进行了同步。如下

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

5)、SerializedCache主要对cache的实现类的值进行序列化,主要作用于添加缓存和获取缓存的方法

@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);
    // 对值进行反序列化
    return object == null ? null : deserialize((byte[]) object);
  }

序列化和反序列化方法如下:

/**
   *  序列化方法
   * @param value
   * @return
   */
  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);
    }
  }

  /**
   * 反序列化方法
   * @param value
   * @return
   */
  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;
  }

6)、FifoCache基于队列先进先出的缓存策略,如下:

 /**
   *  存取key的队列
   */
  private final Deque<Object> keyList;
  private int size;

  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    // 双向链表
    this.keyList = new LinkedList<>();
    // 队列值大小
    this.size = 1024;
  }

主要增强的方法如下

 @Override
  public void putObject(Object key, Object value) {
    // 检测是否超出队列容量大小,并进行相关操作
    cycleKeyList(key);
    delegate.putObject(key, value);
  }
private void cycleKeyList(Object key) {
    // 向尾部添加
    keyList.addLast(key);

    if (keyList.size() > size) {
      //当队列值大于size值时,将最先缓存的数据key从队列删除
      Object oldestKey = keyList.removeFirst();
      // 清空缓存
      delegate.removeObject(oldestKey);
    }
  }

7)、LruCache基于最少使用的淘汰机制实现Cache

// 保存key的map
 private Map<Object, Object> keyMap;
 // 最久的键
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }
public void setSize(final int size) {
    //最近访问的放在最前面,最早访问的放在最后面
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      /**
       *  当新添加entry时,判断是否去除最旧的entry
       *  在原方法中返回false,需要重写
       * @param eldest
       * @return
       */
      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };

  }
@Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    // 当添加新的缓存是,判断是否需要进行淘汰
    cycleKeyList(key);
  }

 private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      // 当eldest不为空,则从delegate移除
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }
  ....其他方法比较简单

8)、WeakCacher通过弱引用的方式对缓存进行管理,需要与ReferenceQueue配合使用,其中定义字段如下:

/**
   *  强引用键的队列,避免缓存被GC
   */
  private final Deque<Object> hardLinksToAvoidGarbageCollection;

  /**
   * 被GC回收WeakEntry集合
   */
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  private final Cache delegate;

  /**
   * hardLinksToAvoidGarbageCollection的大小
   */
  private int numberOfHardLinks;

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

WeakCache 中缓存项的 value 是WeakEntry对象, WeakEntry继承 WeakReference其中指向key 的引用是强引用, 而指向 value 的引用是软引用 ,WeakEntry实现如下:

 // 由于多了一个缓存健值,所以继承WeakReference
  private static class WeakEntry extends WeakReference<Object> {
    private final Object key;

    private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
      // 指向 value 的引用是软引用,且关联了引用队列
      super(value, garbageCollectionQueue);
      // 强引用
      this.key = key;
    }
  }

getObject和putObject方法如下:

@Override
  public void putObject(Object key, Object value) {
    // 移除已经被GC回收的WeakEntry
    removeGarbageCollectedItems();
    // 添加到delegate中
    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对象
    WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
    // 若weakReference不为空
    if (weakReference != null) {
      // 获取值
      result = weakReference.get();
      if (result == null) {
        // 结果为空,说明已经被GC了,从delegate中移除
        delegate.removeObject(key);
      } else {
        // 不为空,添加到队头,防止被GC
        hardLinksToAvoidGarbageCollection.addFirst(result);
        // 当队列size大于指定值时,移除队尾的元素
        if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
          hardLinksToAvoidGarbageCollection.removeLast();
        }
      }
    }
    return result;
  }

WeakCache.removeObject() 方法在清 缓存 前,也会调用 removeGarbageCollectedltems()方法清理被 GC 回收的缓存项,比较简单。
9)SoftCache与WeakCache相似,只不过时软引用。

3、CacheKey:因为 MyBatis 中的缓存键不是一个简单的 String ,而是通过多个对象组成。所以 CacheKey 可以理解成将多个对象放在一起,计算其缓存键。
其中定义的字段如下

/**
   *  参与计算hashcode,默认为37
   */
  private static final int DEFAULT_MULTIPLIER = 37;

  /**
   *
   */
  private static final int DEFAULT_HASHCODE = 17;
  /**
   * hashcode 求值的系数
   */
  private final int multiplier;

  /**
   *  缓存键的hashcode
   */
  private int hashcode;

  /**
   *  校验合
   */
  private long checksum;

  /**
   * updateList 集合的个数
   */
  private int count;
  // 8/21/2017 - Sonarlint flags this as needing to be marked transient.  While true if content is not serializable, this is not always true and thus should not be marked transient.
  /**
   * 由该集合中的所有对象共同决定两个 CacheKey 是否相同
   */
  private List<Object> updateList;

  public CacheKey() {
    this.hashcode = DEFAULT_HASHCODE;
    this.multiplier = DEFAULT_MULTIPLIER;
    this.count = 0;
    this.updateList = new ArrayList<>();
  }

在向 acheKey.updateList 集合中添加对象时,使用的是 CacheKey.update() 方法,具体实现如下:

public void updateAll(Object[] objects) {
    for (Object o : objects) {
      update(o);
    }
  }

而update方法如下:

public void update(Object object) {
    // 根据Object进行相应特征值的计算
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;

    hashcode = multiplier * hashcode + baseHashCode;

    // 添加updateList中
    updateList.add(object);
  }

比较简单,可以到Test中cache模块下的CacheKey类进行调试,调试一下立刻就会明白。
Cache缓存模块至此完毕!!

参考文献《Mybatis技术内幕》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值