Mybatis缓存(一)

Mybatis查询,默认开启了一级缓存,二级缓存需要手动开启。针对于缓存,Mybatis提供了Cache这个组件进行对查询出来的数据进行缓存,而Cache作为Mybatis混存的基本组件,所以想要对其功能进行拓展但是又不能违反开闭原则,所以将采用装饰器模式。


浅谈装饰器模式

  • Component(组件)*:组件接口定义了全部组件实现类以及所有装饰器实现的行为
  • ConcreteDecorator(具体组件实现类)*:具体组件实现类实现了Component接口,通常情况下具体组件实现类就是被装饰器装饰的原始对象,该类提供了Component接口定义的最基本功能,其余需要拓展的其他功能都需要通过装饰器对组件进行装饰
  • Decorate(装饰器)*:所有装饰器的父类,实现了一个Compoent接口的抽象类,并且在其中还封装了一个Component对象,也就是被装饰的对象
QQ图片20200923205331.png

Cache

相当于是装饰器里最基本组件,被装饰器装饰的对象,拓展其实现的功能

public interface Cache {
    // 获取缓存对象ID
  String getId();
  // 添加缓存
  void putObject(Object key, Object value);
  // 根据缓存对象ID去获取对象
  Object getObject(Object key);
  // 删除缓存
  Object removeObject(Object key);
  // 清空缓存
  void clear();
  // 获取缓存的个数
  int getSize();
  // 获取读写锁
  ReadWriteLock getReadWriteLock();

}

实现类就是通过不断地装饰将Cache组件层级注入,多重装饰

Cache实现类顺序.jpg

PerpetualCache 永久缓存

相当于是实现了需要被装饰的组件的最基本的实现类,

public class PerpetualCache implements Cache {
//  Cache对象的唯一标识
  private final String id;
//  声明了一个HashMap去当作缓存
  private Map<Object, Object> cache = new HashMap<>();
...
@Override
...

BlockingCache 阻塞缓存

阻塞式的缓存装饰器,保证只有一个线程到数据库中查找指定key对应的数据,

public class BlockingCache implements Cache {

//  设置缓存的超时时间
  private long timeout;
//  由于使用了装饰器模式,所以需要将Cache组件注入到装饰器中
  private final Cache delegate;
//  使用ConcurrentHashMap,支持检索的完全并发性和更新的高预期并发性的哈希表,每个Key都有对应的ReentrantLock对象
  private final ConcurrentHashMap<Object, ReentrantLock> locks;
}

由下图可知,当多个线程去查询BlockingCache会被阻塞,因为BlockingCache是单线程操作,需要等待,查询成功后才会释放锁唤醒阻塞在该锁上的线程。


多线程查询.jpg

而对于BlockingCache设置缓存,由于BlockingCache通过ConcurrentHashMap去加载缓存数据,所以生成锁和key的规则由ConcurrentHashMap决定

  • BlockingCache生成缓存
  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
//    如果添加缓存失败,则会释放锁
      releaseLock(key);
    }
  }
  • BlockingCache读取缓存
  @Override
  public Object getObject(Object key) {
//  获得锁
    acquireLock(key);
//  查询Key
    Object value = delegate.getObject(key);
//  缓存有key对应的缓存项,释放锁否则继续持有锁
    if (value != null) {
      releaseLock(key);
    }
    return value;
  }
  • Cache对象读取缓存的时候会涉及到ConcurrentHashMap的获取Key,

  private ReentrantLock getLockForKey(Object key) {
//  如果指定的键尚未与锁相关联,则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非null,将会报错 。 
    return locks.computeIfAbsent(key, k -> new ReentrantLock());
  }
//  获得锁
  private void acquireLock(Object key) {
//  根据key去获取ReentrantLock对象
    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();
    }
  }

由下图,假设线程A 先发起请求对数据进行查询,线程A获取BlockingCache的锁,先访问缓存,由于缓存中没有数据所以将查询数据库,将数据库里的数据查询出来并加载到缓存中,并且释放线程A的锁,同时唤醒阻塞在Blocking Cache上的其中一条线程,如果其他线程查询的内容和线程A查询的数据一致则会通过KeyA在Blocking Cache获取数据,而不是再次访问数据库。


多线程查询.jpg

ScheduledCache 周期性清理缓存装饰器

设置了定期清理缓存的时间,lastClear记录了最后一次清理缓存的时间,在默认设置的时间间隔之后会再次清理缓存

public class ScheduledCache implements Cache {
    // 使用装饰器模式,对Cache组件进行装饰
    private final Cache delegate;
    // 明确的时间间隔
    protected long clearInterval;
    // 最后的清理时间
    protected long lastClear;
  
  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    // 1 hour默认清理缓存的时间间隔是一小时
    this.clearInterval = 60 * 60 * 1000; 
    this.lastClear = System.currentTimeMillis();
  }
}

FifoCache:FIFO(先入先出)缓存装饰器。

 public class FifoCache implements Cache {
 
   private final Cache delegate;
  // Deque(双端队列),支持两端元素插入和移除的线性集合。  当使用deque作为队列时,FIFO(先进先出)行为的结果
   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) {
     //    循环键列表,判断List是否占满了,占满了则删除第一个数据
     cycleKeyList(key);
     delegate.putObject(key, value);
   }
 

LruCache : 最近最少使用的缓存装饰器

public class LruCache implements Cache {

  private final Cache delegate;
  // 一个有序的HashMap保存每个缓存对象的加载顺序
  private Map<Object, Object> keyMap;
  // 最早加载进来的缓存对象
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    //  设置缓存的大小为1024
    setSize(1024);
  }
  • LruCache设置缓存,使用LinkedHashMap
public void setSize(final int size) {
  // 新建一个LinkedHashMap,三个参数分别为:初始容量初始容量,装载因子装载因子,排序顺序(为TRUE时是顺序
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      //判断加载的缓存数量是否超过缓存List的大小
      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

SoftCache : 软引用缓存装饰器

public class SoftCache implements Cache {
  private final Cache delegate;
  //    硬连接,避免垃圾回收,将需要缓存的SoftEntry加载到该集合中
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  //    垃圾收集项队列用来记录已经被GC回收的缓存SoftEntry对象
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  //    强连接的个数,默认是256个
  private int numberOfHardLinks;

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

LoggingCache :日志缓存装饰器

会记录缓存被访问的次数和缓存被命中的次数,并且按照自定的日志规则返回这两个值

public class LoggingCache implements Cache {

//  添加日志组件
  private final Log log;
//  被装饰对象的组件
  private final Cache delegate;
//  请求的次数
  protected int requests = 0;
//  缓存击中的次数
  protected int hits = 0;

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

SynchronizedCache : 同步缓存装饰器

public class SynchronizedCache implements Cache {
//  需要被装饰的对象
  private final Cache delegate;
//  将装饰对象被一个设置一个锁,为Cache添加了同步的功能
  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }
}

本篇详细说明了,Mybatis的缓存Cache以及为了拓展其功能使用的装饰器模式生成的其他不同组件,后续会对访问缓存进行详解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。