1. 程式人生 > >MyBatis的二級緩存

MyBatis的二級緩存

ret put highlight locking serial lse linked pre ase

目錄

  1. Mybatis中如何配置二級緩存

  2. Cache解析處理過程

  3. Cache支持的過期策略
  4. 裝飾器源碼

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擴展增加功能。下面對其裝飾器源碼進行分析

/**
 * 簡單阻塞裝飾器
 *
 * 當前緩存中不存在時對緩存緩存的key加鎖,其它線程就只能一直等到這個元素保存到緩存中
 * 由於對每個key都保存了鎖對象,如果在大量查詢中使用可能存在OOM都風險
 * @author Eduardo Macarron
 *
 */
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;
  }  
}

  

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

}

  

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

}

  

  

 

  

MyBatis的二級緩存