参考博客:https://www.cnblogs.com/happyflyingpig/p/7739749.html
Mybatis的执行器
BaseExecutor执行器使用的是一级缓存
CachingExecutor执行器使用的是二级缓存
1.一级缓存
1.1整体架构图
1.2一级缓存的实现(源码级别)
一级缓存其实通俗地来讲就是,在sqlsession里面创建一个本地缓存,然后第二次进行相同的查询时候,就不会到数据库里面进行查找,而是从缓存中获取结果集。Mybatis利用HashMap实现缓存
1.2.1一级缓存的存储类PerpetualCache
public class PerpetualCache implements Cache {
//一级缓存的ID
private final String id;
//利用HashMap缓存结果集
private Map<Object, Object> cache = new HashMap<Object, Object>();
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 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();
}
}
Mybatis的内部一级缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。
SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。
1.2.2一级缓存的存储时机(BaseExecutor中的方法)先查缓存,缓存中如果没有,则查询数据库
查询入口query函数(queryStack的作用:用来记录嵌套查询的层数)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//检查当前executor是否已经关闭
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()) {
//非嵌套查询,并且<select>节点配置的flushCache属性为true时,才会清空一级缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;//增加查询层数
//查询一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//1.处理结果
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//其中会调用doQuery方法完成数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;//当前查询完成,查询层数减少
}
if (queryStack == 0) {
//延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
执行queryFromDatabase函数从数据库中查询结果
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//执行数据库的查询操作,调用子类方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//将从数据库中查询到的结果添加到缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
2.二级缓存
参考博客:https://blog.csdn.net/luanlouis/article/details/41408341#commentBox
参考博客:https://www.cnblogs.com/happyflyingpig/p/7739749.html
CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式,CachingExecutor和Executor的接口的关系如下类图所示:
2.1 使用二级缓存需要的配置
MyBatis对二级缓存的支持粒度很细,它会指定某一条查询语句是否使用二级缓存。
虽然在Mapper中配置了<cache>,并且为此Mapper分配了Cache对象,这并不表示我们使用Mapper中定义的查询语句查到的结果都会放置到Cache对象之中,我们必须指定Mapper中的某条选择语句是否支持缓存,即如下所示,在<select> 节点中配置useCache="true",Mapper才会对此Select的查询支持缓存特性,否则,不会对此Select查询,不会经过Cache缓存。如下所示,Select语句配置了useCache="true",则表明这条Select语句的查询会使用二级缓存。
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" useCache="true">
总之,要想使某条Select查询支持二级缓存,你需要保证:
1. MyBatis支持二级缓存的总开关:全局配置变量参数 cacheEnabled=true
2. 该select语句所在的Mapper,配置了<cache> 或<cached-ref>节点,并且有效
3. 该select语句的参数 useCache=true
2.2 二级缓存实现的选择
MyBatis对二级缓存的设计非常灵活,它自己内部实现了一系列的Cache缓存实现类,并提供了各种缓存刷新策略如LRU,FIFO等等;另外,MyBatis还允许用户自定义Cache接口实现,用户是需要实现org.apache.ibatis.cache.Cache接口,然后将Cache实现类配置在<cache type="">节点的type属性上即可;除此之外,MyBatis还支持跟第三方内存缓存库如Memecached的集成,总之,使用MyBatis的二级缓存有三个选择:
1.MyBatis自身提供的缓存实现;
2. 用户自定义的Cache接口实现;
3.跟第三方内存缓存库的集成;
2.2.1 MyBatis自身提供的缓存实现;
2.2.1 二级缓存的配置
①MyBatis支持二级缓存的总开关:全局配置变量参数 cacheEnabled=true
②该select语句所在的Mapper,配置了<cache> 或<cached-ref>节点,并且有效
③该select语句的参数 useCache=true
2.2.2 二级缓存使用的执行器(CachingExecutor类)
①这里的CachingExecutor是装饰类
②当进行查询操作时,执行query函数
③TransactionalCacheManager和TransactionalCache是CachingExecutor依赖的两个组件,TransactionalCacheManager和TransactionalCache的关系如下
将查询到的结果集先存储在TransactionalCache类中的private final Map<Object, Object> entriesToAddOnCommit;map中
TransactionalCache.putObject()方法并没有直接将结果对象记录到其封装的二级缓存中,而是暂时保存在entriesToAddOnCommit集合中,在事务提交时才会将这些结果对象从entriesToAddOnCommit集合添加到二级缓存中。
二级缓存使用的缓存类型
事务提交时执行CachingExecutor执行器的commit方法
执行flushPendingEntries函数将记录添加到二级缓存中
当配置文件中不设置缓存策略时,使用的默认缓存实现类如下图所示