MyBatis做爲一個強大的持久層框架,緩存是其必不可少的功能之一,MyBatis中的緩存是兩層結構的,分爲一級緩存,二級緩存,但本質上市相同的,它們使用的都是Cache接口的實現。java
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(); } }
下面介紹一下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(); } }