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

      

      

     

      

  • 相关阅读:
    Roce ofed 环境搭建与测试
    Ubuntu 1804 搭建NFS服务器
    Redhat 8.0.0 安装与网络配置
    Centos 8.1 安装与网络配置
    SUSE 15.1 系统安装
    VSpare ESXi 7.0 基本使用(模板、iso、SRIOV)
    VSpare ESXi 7.0 服务器安装
    open SUSE leap 15.1 安装图解
    KVM虚拟机网卡连接网桥
    GitHub Action一键部署配置,值得拥有
  • 原文地址:https://www.cnblogs.com/wei-zw/p/8903977.html
Copyright © 2020-2023  润新知