MyBatis作为一个强大的持久层框架,缓存是其必不可少的功能之一,MyBatis中的缓存是两层结构的,分为一级缓存,二级缓存,但本质上市相同的,它们使用的都是Cache接口的实现。
一 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 记录缓存项。代码如下
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
二 装饰器(BlockingCache)
下面介绍一下cache.decorators 包下的装饰器,他们都直接实现了cache接口,扮演者ConcreteDecorator的角色。这些装饰器会在PerpetualCache的基础上提供一下额外的功能,通过租后满足一定的需求。
1 BlockingCache 的get
BlockingCache 是阻塞版本的缓存装饰器,它保证只有一个线程到数据库中查找指定key对应的数据。
加入线程A 在BlockingCache 中未查找到keyA对应的缓存项时,线程A会获取keyA对应的锁,这样后续线程在查找keyA是会发生阻塞,如下图所示
代码实现如下
@Overridepublic 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 对象,再加锁;如果获取失败,则阻塞一段时间。
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 ConcurrentHashMaplocks; * 尝试添加到locks集合中,如果locks集合中已经有了相应的Reentrantock对象,则使用原有的locks 中的ReentrantLock对象 **/ ReentrantLock previous = locks.putIfAbsent(key, lock); return previous == null ? lock : previous;}
1 BlockingCache 的put
假如线程A从数据库中查找到keyA对应的结果对象后,将结果放入到BlockingCache 中,此时线程A会释放keyA对应的锁,唤醒阻塞在该锁上的线程,其它线程可以从缓存中获取数据,而不是再次访问数据库。
@Overridepublic 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(); }}