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<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对应的数据。
加入线程A 在BlockingCache 中未查找到keyA对应的缓存项时,线程A会获取keyA对应的锁,这样后续线程在查找keyA是会发生阻塞,如下图所示

代码实现如下
@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 对象,再加锁;如果获取失败,则阻塞一段时间。
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
假如线程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();
}
}
本文介绍了MyBatis中的缓存机制,包括一级缓存和二级缓存,并详细解析了Cache接口及其核心实现PerpetualCache。此外还介绍了装饰器模式下的BlockingCache,解释了它是如何确保线程安全地访问数据库。

1万+

被折叠的 条评论
为什么被折叠?



