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

      

      

     

      

  • 相关阅读:
    windows 10 下部署WCF 一些细节
    系统提示 由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。
    zookeeper常用命令
    zookeeper安装与配置
    Java访问者模式
    总结设计模式—(大话设计模式下篇)
    总结设计模式—(大话设计模式中篇)
    总结设计模式—(大话设计模式上篇)
    Java中间缓存变量机制
    解释模式
  • 原文地址:https://www.cnblogs.com/wei-zw/p/8903977.html
Copyright © 2020-2023  润新知