MyBatis的二级缓存

Posted wei_zw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis的二级缓存相关的知识,希望对你有一定的参考价值。

目录

  1. Mybatis中如何配置二级缓存

  2. Cache解析处理过程

  3. Cache支持的过期策略
  4. 装饰器模式
  5. 装饰器源码

Mybatis中如何配置二级缓存

基于注解配置缓存

@CacheNamespace(blocking=true)
public interface PersonMapper {

  @Select("select id, firstname, lastname from person")
  public List<Person> findAll();
}

 

基于XML配置缓存

<mapper namespace="org.apache.ibatis.submitted.cacheorder.Mapper2">

	<cache/>

</mapper>

 

Cache解析处理过程

 

为什么配置了一个<cache/>就可以使用缓存了呢?通过下面的源码可以发现,缓存配置是有默认值的

private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      //获取配置的type值,默认值为PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      //获取type的class
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      //获取配置过期策略,默认值为LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //获取配置的刷新间隔
      Long flushInterval = context.getLongAttribute("flushInterval");
      //获取配置的缓存大小
      Integer size = context.getIntAttribute("size");
      //是否配置了只读,默认为false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      //是否配置了阻塞,默认为false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  Cache支持的过期策略

    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

  缓存的基本实现

public class PerpetualCache implements Cache {

  //缓存ID
  private final String id;
  
  //缓存
  private Map<Object, Object> cache = new HashMap<Object, Object>();

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

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

}

  

      在Mybatis中是不是根据不通的过期策略都创建不通都缓存呢?实际上Mybatis的所有Cache算法都是基于装饰器模式对PerpetualCache扩展增加功能。下面简单介绍一下装饰器(Decorator)模式以及在Mybatis装饰器实现源码

 装饰器模式

     装饰器模式以客户透明对方式动态给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰器模式可以在不创造更多子类的情况下对对象对功能加以扩展。

     装饰器模式常常被称为包裹模式,就是因为每一个装饰器实现类,都是将下一个装饰器或真实实现包裹起来。这样做可以将真实实现简化逻辑,同时更容易扩展新功能。

 Mybatis装饰器

private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      
      if (clearInterval != null) {
        //在cache基础上加上ScheduledCache装饰器
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      //如果配置只读则增加序列化功能
      if (readWrite) {
        //增加序列化装饰器
        cache = new SerializedCache(cache);
      }
      //增加日志装饰器
      cache = new LoggingCache(cache);
      //增加同步装饰器
      cache = new SynchronizedCache(cache);
      if (blocking) {
        //增加阻塞读装饰器
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

  

简单阻塞装饰器, 当前缓存中不存在时对缓存缓存的key加锁,其它线程就只能一直等到这个元素保存到缓存中由于对每个key都保存了锁对象,如果在大量查询中使用可能存在OOM都风险

public class BlockingCache implements Cache {
  //超时时间
  private long timeout;
  //委派代表
  private final Cache delegate;
  //缓存key和锁的映射关系
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
  }

  //获取ID,直接委派给delegate处理
  @Override
  public String getId() {
    return delegate.getId();
  }

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

  //放置缓存,结束后释放锁; 注意在方缓存前是没有加锁的
  //该处设置是和获取缓存有很大关系
  @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);
    //如果缓存数据存在则释放锁,否则返回,注意,此时锁没有释放;下一个线程获取的时候是没有办法
    //获取锁,只能等待;记住 put结束的时候会释放锁,这里就是为什么put之前没有获取锁,但是结束后要释放锁的原因
    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;
  }

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

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
  
  private ReentrantLock getLockForKey(Object key) {
    ReentrantLock lock = new ReentrantLock();
    ReentrantLock previous = locks.putIfAbsent(key, lock);
    return previous == null ? lock : previous;
  }
  
  private void acquireLock(Object 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) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }  
}

  LRU缓存,LRU算法主要通过LinkedHashMap实现,实现简单明了

public class LruCache implements Cache {

  private final Cache delegate;
  //key映射表
  private Map<Object, Object> keyMap;
  //最老的key
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }

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

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

  public void setSize(final int size) {
    //使用LinedListHashMap实现LRU, accessOrder=true 会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;


      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        //如果当前大小已经超过1024则删除最老元素
        boolean tooBig = size() > size;
        if (tooBig) {
          //将最老元素赋值给eldestKey
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

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

  @Override
  public Object getObject(Object key) {
    //每次反问都会触发keyMap的排序
    keyMap.get(key); 
    return delegate.getObject(key);
  }

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

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

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

  private void cycleKeyList(Object key) {
    //将当前key放入keyMap
    keyMap.put(key, key);
    //如果最老的key不为null则清除最老的key的缓存
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

  FIFO缓存,通过LinkedList实现了FIFO算法

public class FifoCache implements Cache {

  private final Cache delegate;
  //双端队列
  private final Deque<Object> keyList;
  private int size;

  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<Object>();
    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) {
    cycleKeyList(key);
    delegate.putObject(key, value);
  }

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

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

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

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

  private void cycleKeyList(Object key) {
    //将当前key添加到队尾
    keyList.addLast(key);
    //如果key的队列长度超过限制则删除队首的key以及缓存
    if (keyList.size() > size) {
      Object oldestKey = keyList.removeFirst();
      delegate.removeObject(oldestKey);
    }
  }

}

  序列化缓存装饰器,对于缓存对象进行了序列化和反序列化避免了值引用问题

/**
 * 序列化缓存
 */
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) {
    //如果对象为null或者实现了Serializable接口的对象需要进行序列化,否则抛出异常
    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);
    //如果对象为null则直接返回null,否则返回反序列化后对象
    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();
      oos.close();
      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();
      ois.close();
    } 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 IOException, ClassNotFoundException {
      return Resources.classForName(desc.getName());
    }
    
  }

}

  软引用缓存装饰器,很好的一个实现方式,在弱引用缓存装饰器中同样使用了该方式实现

public class SoftCache implements Cache {
  //强引用队列,包装队列中的元素不会被垃圾回收
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  //引用队列,被回收对象在添加到引用队列中
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  //被包装的实现
  private final Cache delegate;
  //保存强引用的数量
  private int numberOfHardLinks;

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

  @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();
    //将当前键值对包装成SoftEntry存入缓存
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  }

  @Override
  public Object getObject(Object key) {
    Object result = null;
    //从缓存中获取软引用对象
    SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
    //如果软引用不为null,则获取引用对象中的真实的对象
    if (softReference != null) {
      result = softReference.get();
      //如果真实的对象为null,则标示该对象已经被垃圾回收了,则删除缓存
      if (result == null) {
        delegate.removeObject(key);
      } else { //真实对象不为null
        synchronized (hardLinksToAvoidGarbageCollection) {
          //将对象添加早强引用的对头
          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() {
    synchronized (hardLinksToAvoidGarbageCollection) {
      hardLinksToAvoidGarbageCollection.clear();
    }
    removeGarbageCollectedItems();
    delegate.clear();
  }

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

  //删除被垃圾回收对象
  private void removeGarbageCollectedItems() {
    SoftEntry sv;
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      delegate.removeObject(sv.key);
    }
  }

  private static class SoftEntry extends SoftReference<Object> {
    private final Object key;

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

}

  定期清空缓存装饰器,在该装饰器中并不是使用主动失效方式,而是使用懒惰方式。

/**
 * 定期清空缓存的装饰器,其清空缓存的策略使用的是懒惰清空方式
 * 在 getSize,putObject, getObject removeObject的时候会触发清空检查
 * 
 */
public class ScheduledCache implements Cache {

  private final Cache delegate;
  //刷新间隔
  protected long clearInterval;
  
  //最后一次清空缓存时间
  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);
  }

  @Override
  public void clear() {
    lastClear = System.currentTimeMillis();
    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 boolean clearWhenStale() {
    //如果当前时间减去最后一次刷新时间大于刷新间隔则需要晴空缓存
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

}

  

  

 

 

 

  

以上是关于MyBatis的二级缓存的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis缓存

mybatis二级缓存详解

真正的mybatis_redis二级缓存

MyBatis系列目录--5. MyBatis一级缓存和二级缓存(redis实现)

使用Redis做MyBatis的二级缓存

mybatis关于二级缓存的配置及源码分析