mybatis源码解读:cache包(缓存机制功能)
1.缓存机制
在进行源码阅读时,通常可以以包为单位进行,因为包本身就是具有一定结构、功能的类的集合,但是,也总会有一些功能相对复杂,会横跨多个包。因此以功能为主线一次阅读多个包中的源码是必要的,能帮助我们理清功能实现。
mybatis基于cache包中提供的缓存实现了两级缓存机制。
1.一级缓存
mybatis的一级缓存又叫本地缓存,与它相关的配置项有2个。
1.在配置文件XML的setting节点,可选项有session与statement,分别对应了一次会话和一条语句,一级缓存的默认范围是session。
2.是在映射文件mapper.xml中的数据库操作节点内增加flushCache属性,默认值为false。可以设置为ture或false;当设置为true时,mybatis会在改数据库操作执行前清空一,二级缓存。
一级缓存功能是由BaseExecutor类实现。BaseExecutor类作为实际执行器的基类,为所有实际执行器提供一些通用的基本功能,在这里增加缓存也就意味着每个实际执行器都具有着一级缓存。
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 查询操作的结果缓存
protected PerpetualCache localCache;
// Callable查询的输出参数缓存
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
/**
* 更新数据库数据,INSERT/UPDATE/DELETE三种操作都会调用该方法
* @param ms 映射语句
* @param parameter 参数对象
* @return 数据库操作结果
* @throws SQLException
*/
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource())
.activity("executing an update").object(ms.getId());
if (closed) {
// 执行器已经关闭
throw new ExecutorException("Executor was closed.");
}
// 清理本地缓存
clearLocalCache();
// 返回调用子类进行操作
return doUpdate(ms, parameter);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return doFlushStatements(isRollBack);
}
/**
* 执行查询操作
* @param ms 映射语句对象
* @param parameter 参数对象
* @param rowBounds 翻页限制
* @param resultHandler 结果处理器
* @param <E> 输出结果类型
* @return 查询结果
* @throws SQLException
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 生成缓存的键
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* 查询数据库中的数据
* @param ms 映射语句
* @param parameter 参数对象
* @param rowBounds 翻页限制条件
* @param resultHandler 结果处理器
* @param key 缓存的键
* @param boundSql 查询语句
* @param <E> 结果类型
* @return 结果列表
* @throws SQLException
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
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()) { // 新的查询栈且要求清除缓存
// 清除一级缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 尝试从本地缓存获取结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
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();
}
deferredLoads.clear();
// 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
因为localCache和localOutputParameterCache都是Executor的属性,而Executor归属sqlSession,因此第一级缓存的最大作用范围便是sqlSession,即一次会话。而insert,update,delete方法都对应了Executor的update方***引发一级缓存的更新。可见一级缓存就是BaseExcecutor中的两个PrepetualCache类型的属性(localCache和localOutputParameterCache),其作用范围有限,不支持各种装饰器的修饰,因此不能进行容量配置,清理策略设置及阻塞设置等。
2.二级缓存
二级缓存的作用范围是一个命名空间(即一个mapper.xml映射文件),而且可以实现多个命名空间共享一个缓存,因此比一级缓存作用范围更广,更为灵活。
二级缓存原理:
二级缓存功能由CacheingExecutor类实现,它是一个装饰器类, 能通过装饰实际执行器为它们增加二级缓存功能。mybatis会根据配置文件中的二级缓存开关配置用CacheingExecutor类装饰实际执行器。
在Configuration类中的newExecutor方法中实现用CacheingExecutor类装饰实际执行器。
/**
* 主要内容分为以下几个部分:
* 1、大量的配置项,和与`<configuration>`标签中的配置对应
* 2、创建类型别名注册机,并向内注册了大量的类型别名
* 3、创建了大量Map,包括存储映射语句的Map,存储缓存的Map等,这些Map使用的是一种不允许覆盖的严格Map
* 4、给出了大量的处理器的创建方法,包括参数处理器、语句处理器、结果处理器、执行器。
* 注意这里并没有真正创建,只是给出了方法。
*/
public class Configuration {
// <environment>节点的信息
protected Environment environment;
/**
* 创建一个执行器
* @param transaction 事务
* @param executorType 数据库操作类型
* @return 执行器
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据数据操作类型创建实际执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 根据配置文件中settings节点cacheEnabled配置项确定是否启用缓存
if (cacheEnabled) { // 如果配置启用缓存
// 使用CachingExecutor装饰实际执行器
executor = new CachingExecutor(executor);
}
// 为执行器增加拦截器(插件),以启用各个拦截器的功能
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}