mybatis的缓存模块在org.apache.ibatis.cache包下,包括如下接口和类
Cache是核心实现接口。每个namespace会创建一个Cache实例,Cache的实现必须要有一个String类型的id作为参数的构造函数,mybatis将传namespace作为id。
public interface Cache {
/**
* 缓存对象的 ID
* @return The identifier of this cache
*/
String getId();
/**
* 向缓存中添加数据,一般情况下 key是CacheKey value是查询结果
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Object value);
/**
* 根据指定的key,在缓存中查找对应的结果对象
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
/**
* As of 3.3.0 this method is only called during a rollback
* for any previous value that was missing in the cache.
* This lets any blocking cache to release the lock that
* may have previously put on the key.
* A blocking cache puts a lock when a value is null
* and releases it when the value is back again.
* This way other threads will wait for the value to be
* available instead of hitting the database.
* 删除key对应的缓存数据
*
* @param key The key
* @return Not used
*/
Object removeObject(Object key);
/**
* Clears this cache instance.
* 清空缓存
*/
void clear();
/**
* Optional. This method is not called by the core.
* 缓存的个数。
* @return The number of elements stored in the cache (not its capacity).
*/
int getSize();
/**
* Optional. As of 3.2.6 this method is no longer called by the core.
*
* Any locking needed by the cache must be provided internally by the cache provider.
* 获取读写锁
* @return A ReadWriteLock
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
}
PerpetualCache是Cache的简单实现,持有一个HashMap变量,作为缓存。
public class PerpetualCache implements Cache {
private final String id; // Cache 对象的唯一标识
// 用于记录缓存的Map对象
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@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;
// 只关心ID
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
// 只关心ID
return getId().hashCode();
}
}
decorators包下类,使用装饰器模式,都继承Cache接口,在PerpetualCache的基础上,扩展功能。
见名知意,这是一个阻塞的缓存,保证只有一个线程能能查询到指定key的缓存。
public class BlockingCache implements Cache {
private long timeout; // 阻塞超时时长
private final Cache delegate; // 被装饰的底层 Cache 对象
// 每个key 都有对象的 ReentrantLock 对象
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
// 被装饰的 Cache 对象
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object value) {
try {
// 执行 被装饰的 Cache 中的方法
delegate.putObject(key, value);
} finally {
// 释放锁
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
acquireLock(key); // 获取锁
Object value = delegate.getObject(key); // 获取缓存数据
if (value != null) { // 有数据就释放掉锁,否则继续持有锁
releaseLock(key);
}
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();
}
private ReentrantLock getLockForKey(Object key) {
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
private void acquireLock(Object key) {
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 {
lock.lock();
}
}
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
缓存实现类 | 描述 | 作用 | 装饰条件 |
---|---|---|---|
基本缓存 | 缓存基本实现类 | 默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类 | 无 |
LruCache | LRU策略的缓存 | 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use) | eviction=“LRU”(默认) |
FifoCache | FIFO策略的缓存 | 当缓存到达上限时候,删除最先入队的缓存 | eviction=“FIFO” |
SoftCacheWeakCache | 带清理策略的缓存 | 通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference | eviction="SOFT"eviction=“WEAK” |
LoggingCache | 带日志功能的缓存 | 比如:输出缓存命中率 | 基本 |
SynchronizedCache | 同步缓存 | 基于synchronized关键字实现,解决并发问题 | 基本 |
BlockingCache | 阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现 | blocking=true |
SerializedCache | 支持序列化的缓存 | 将对象序列化以后存到缓存中,取出时反序列化 | readOnly=false(默认) |
ScheduledCache | 定时调度的缓存 | 在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存–即每隔一段时间清空一次缓存 | flushInterval不为空 |
TransactionalCache | 事务缓存 | 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 | 在TransactionalCacheManager中用Map维护对应关系 |
@Test
public void test2() throws Exception{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer param = 1;
User user = mapper.selectUserById(param);
System.out.println(user);
System.out.println("------------间隔---------------");
// 第二次查询
User user2 = mapper.selectUserById(param);
System.out.println(user2);
// 5.关闭会话
sqlSession.close();
}
一级缓存(local cache)的作用域是同一个SqlSession。默认开启,如果要关闭,设置参数localCacheScope:STATEMENT。
官方文档:https://mybatis.org/mybatis-3/configuration.html#settings。
作用域:
如何开启
注意:
在初始化XMLConfigBuilder的构造函数,初始化Configuration对象的时候创建别名。
在解析完settings标签之后,在settingsElement(Properties props) 方法中设置参数。
cacheEnabled默认值:true
localCacheScope默认值:SESSION。
解析映射文件时候:configurationElement(XNode context)
具体参数的解析:
具体可以查看官方文档:https://mybatis.org/mybatis-3/sqlmap-xml.html
继续:
然后我们可以发现 如果存储 cache 标签,那么对应的 Cache对象会被保存在 currentCache 属性中。
进而在 Cache 对象 保存在了 MapperStatement 对象的 cache 属性中。
CachingExecutor的方法,这里查询二级缓存
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement()
// 由 标签决定 控制二级缓存的开关有两个 一个是 CacheEnabled 全局配置文件中的settings 配置 --》 装饰执行器 Executor CachingExecutor
// 一个是 cache 标签 映射文件中的配置 创建 Cache 对象
if (cache != null) {
// flushCache="true" 清空一级二级缓存 >>
flushCacheIfRequired(ms);
// 在 select 标签中 配置了 useCache 属性
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 获取二级缓存
// 缓存通过 TransactionalCacheManager、TransactionalCache 管理
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 表示二级缓存中没有数据 那么数据进一步查询处理
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 写入二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor的查询方法,这里查询一级缓存。
/**
* queryStack 和 占位符的作用
* 1.查询 学校 正常查询 --> 放入一级缓存
* 2.查询 学校的学生 正常查询 --> 放入一级缓存
* 3.查询 学生的学校 延迟加载,从缓存中获取
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 异常体系之 ErrorContext
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 防止递归查询重复处理缓存
queryStack++;
// 查询一级缓存
// ResultHandler 和 ResultSetHandler的区别 一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存中有数据 说明查询的数据在一级缓存中存在(本地缓存)
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 真正的查询流程 一级缓存二级缓存都没有 直接查询数据库中的数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
// 延迟加载的内容
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// 如果LocalCacheScope的值设置为 STATEMENT 则一级缓存失效
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}