Mybatis緩存模塊(一)BlockingCache

MyBatis做爲一個強大的持久層框架,緩存是其必不可少的功能之一,MyBatis中的緩存是兩層結構的,分爲一級緩存,二級緩存,但本質上市相同的,它們使用的都是Cache接口的實現。java

 一 Cache 接口及其實現

 

public interface Cache {

  // 該緩存對象的Id
  String getId();

  //向緩存中添加數據
  void putObject(Object key, Object value);

 //根據key 在緩存中查找結果
  Object getObject(Object key);

  // 刪除key 對應的緩存項
  Object removeObject(Object key);

  // 清空緩存
  void clear();

  //緩存項個數
  int getSize();
  
  // 獲取讀寫鎖
  ReadWriteLock getReadWriteLock();

}

Cache 接口 有多個實現。在Idea 中Ctrl+Alt+shift+U能夠查看類圖數據庫

這些類中大部分都是裝飾器(裝飾器模式),只有PrepetualCache提供了Cache接口的基本實現。PrepetualCache在緩存中扮演着ConcreteComponent的角色,其底層實現比較簡單,使用HashMap 記錄緩存項。代碼以下apache

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {
  
  // 緩存對象的惟一標識
  private String id;
  //記錄緩存項
  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }
  
  // map的大小
  @Override
  public int getSize() {
    return cache.size();
  }
  // hashMap 中添加緩存對象
  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }
  // hashMap 中查找緩存對象
  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }
  // hashMap 中刪除對象
  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }
  // 清空hashMap
  @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();
  }

}

二 裝飾器(BlockingCache

 下面介紹一下cache.decorators 包下的裝飾器,他們都直接實現了cache接口,扮演者ConcreteDecorator的角色。這些裝飾器會在PerpetualCache的基礎上提供一下額外的功能,經過租後知足必定的需求。緩存

 1  BlockingCache 的get框架

   BlockingCache 是阻塞版本的緩存裝飾器,它保證只有一個線程到數據庫中查找指定key對應的數據。ide

  加入線程A 在BlockingCache 中未查找到keyA對應的緩存項時,線程A會獲取keyA對應的鎖,這樣後續線程在查找keyA是會發生阻塞,以下圖所示ui

  

代碼實現以下this

@Override
public Object getObject(Object key) {
  // 獲取key對應的鎖
  acquireLock(key);
  // 查詢key
  Object value = delegate.getObject(key);
  if (value != null) {
    // 若是從緩存(PrepetualCache是用HashMap實現的)中查找到,則釋放鎖,不然繼續持有鎖
    releaseLock(key);
  }        
  return value;
}

 

acquireLock() 方法會嘗試獲取指定的能夠對應的鎖。若是該key沒有對應的鎖對象則爲其建立新的ReentrantLock 對象,再加鎖;若是獲取失敗,則阻塞一段時間。spa

 

private void acquireLock(Object 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 {
    // 若是timeout<=0 既沒有時間設置,直接獲取鎖
    lock.lock();
  }
}
private ReentrantLock getLockForKey(Object key) {
 // 建立ReentrantLock對象
  ReentrantLock lock = new ReentrantLock();
  /**
   *private final ConcurrentHashMap<Object,ReentrantLock> locks;
   * 嘗試添加到locks集合中,若是locks集合中已經有了相應的Reentrantock對象,則使用原有的locks 中的ReentrantLock對象
  **/
  ReentrantLock previous = locks.putIfAbsent(key, lock);
  return previous == null ? lock : previous;
}

 1  BlockingCache 的put.net

   假如線程A從數據庫中查找到keyA對應的結果對象後,將結果放入到BlockingCache 中,此時線程A會釋放keyA對應的鎖,喚醒阻塞在該鎖上的線程,其它線程能夠從緩存中獲取數據,而不是再次訪問數據庫。

 

@Override
public void putObject(Object key, Object value) {
  try {
    // 向緩存中添加緩存頁
    delegate.putObject(key, value);
  } finally {
    // 釋放鎖
    releaseLock(key);
  }
}
private void releaseLock(Object key) {
  ReentrantLock lock = locks.get(key);
  // 鎖是否被當前線程持有
  if (lock.isHeldByCurrentThread()) {
   // 釋放所
    lock.unlock();
  }
}
相關文章
相關標籤/搜索