上一篇[MyBatis框架原理2:SqlSession运行过程][1]介绍了MyBatis的工作流程,其中涉及到了MyBatis缓存的使用,首先回顾一下工作流程图:

如果开启了二级缓存,数据查询执行过程就是首先从二级缓存中查询,如果未命中则从一级缓存中查询,如果也未命中则从数据库中查询。MyBatis的一级和二级缓存都是基于Cache接口的实现,下面先来看看Cache接口和其各种实现类。
Cache接口及常用装饰器
public interface Cache { String getId(); //缓存中添加数据,key为生成的CacheKey,value为查询结果 void putObject(Object key, Object value); //查询 Object getObject(Object key); //删除 Object removeObject(Object key); //清空缓存 void clear(); //获取缓存数量 int getSize(); //获取读写锁 ReadWriteLock getReadWriteLock(); }Cache接口位于MyBatis的cache包下,定义了缓存的基本方法,其实现类采用了装饰器模式,通过实现类的组装,可以实现操控缓存的功能。cache包结构如下:

- PerpetualCache是Cache接口的实现类,通过内部的HashMap来对缓存进行基本的操作,通常配合装饰器类一起使用。
- BlockingCache装饰器:保证只有一个线程到数据库中查询指定key的数据,如果该线程在BlockingCache中未查找到数据,就获取key对应的锁,阻塞其他查询这个key的线程,通过其内部ConcurrentHashMap来实现,源码如下:
public class BlockingCache implements Cache { //阻塞时长 private long timeout; private final Cache delegate; //key和ReentrantLock对象一一对应 private final ConcurrentHashMap<Object, ReentrantLock> locks; @Override public Object getObject(Object key) { //获取key的锁 acquireLock(key); //根据key查询 Object value = delegate.getObject(key); //如果命中缓存,释放锁,未命中则继续持有锁 if (value != null) { releaseLock(key); } return value; } @Override //从数据库获取结果后,将结果放入BlockingCache,然后释放锁 public void putObject(Object key, Object value) { try { delegate.putObject(key, value); } finally { releaseLock(key); } } ...- FifoCache装饰器: 先入先出规则删除最早的缓存,通过其内部的Deque实现。
- LruCache装饰器: 删除最近使用最少的缓存, 通过内部的LinkedHashMap实现。
- SynchronizedCache装饰器:同步Cache。
- LoggingCache装饰器: 提供日志功能,记录和输出缓存命中率。
- SerializedCache装饰器:序列化功能。
CacheKey
CacheKey对象是用来确认缓存项的唯一标识,由其内部ArrayList添加的所有对象来确认两个CacheKey是否相同,通常ArrayList内将添加MappedStatement的id,SQL语句,用户传递给SQL语句的参数以及查询结果集范围RowBounds等,CacheKey源码如下:
public class CacheKey implements Cloneable, Serializable { ... private final int multiplier; private int hashcode; private long checksum; private int count; private List<Object> updateList; public CacheKey() { this.hashcode = DEFAULT_HASHCODE; this.multiplier = DEFAULT_MULTIPLYER; this.count = 0; this.updateList =
