一、缓存的意义
我们都知道程序中缓存的意义是为了减少一些重复的操作,而把结果存储起来重复利用。同样的,对于数据库操作来说,缓存结果可以减少与数据库的交互,提升程序性能。
二、缓存的作用域
对于数据库查询结果的缓存,缓存的结构自然就是《 执行的 SQL : 执行的结果》。
而考虑到数据库事务隔离的特点,同一个事务中相同的 SQL 查询结果应该是一致的,对应 Mybatis 的一个 SqlSession。在这个级别中,同一个 SQL 不会重复执行,而是缓存第一次结果,之后这个 SQL 所有查询都从缓存中获取,当然对于一次查询 SqlSession 的结束,SqlSession 对象就会被回收,也就是缓存也会失效被回收。
而有一些数据一般不会被修改,所以同一个 SQL 几乎大部分执行返回结果是一致的,也就是默认所有的事务看到的结果是一致的,所以 Mybatis 也为这种情况设计了缓存,这种缓存对于所有的 SqlSession 都是可见的。
三、同一个事务可见
也就是 SqlSession 级别,这个级别中,相同的 SQL 结果会被缓存起来,之后查询不查询数据库。
使用前需要以下配置: 无(默认开启且支持,不支持关闭)
Java 中实现:
public abstract class BaseExecutor implements Executor {
// 一级缓存器
protected PerpetualCache localCache;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.localCache = new PerpetualCache("LocalCache");
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 第一次查询或者SQL设置了立即提交事务到数据库,则清空缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从一级缓存中获取结果,获取到则直接返回,不查询数据库
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--;
}
// 省略部分语句
return list;
}
复制代码
四、所有事务可见
也就是 Namespace 级别,在这个级别中,数据被缓存在 MappedStatement 的 Cache 对象中,
当然这个里面的 Cache 不是简单的 《 执行的 SQL : 执行的结果》,而是 《事务对象 : 《 执行的 SQL : 执行的结果》》
public final class MappedStatement {
private Cache cache;
private boolean useCache;
private String id;
}
复制代码
使用前需要以下配置,配置文件开启二级缓存(默认为 false)
mybatis.configuration.cache-enabled=true
复制代码
XML 的 SQL 配置使用二级缓存
<select id="XX" useCache="true">
// SQL
</select>
复制代码
Java 代码中实现
//org.apache.ibatis.executor.CachingExecutor#query()
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
// 配置了立即提交事务到数据库,则清空缓存
flushCacheIfRequired(ms);
// 配置了使用二级缓存
if (ms.isUseCache() && resultHandler == null) {
。。。
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;
}
}
// 不使用二级缓存
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
复制代码
五、Mybatis 是怎么实现启用一二级缓存
根据上面的几个小节,我们知道一级缓存是默认支持的,而二级缓存是需要我们手动配置开启的,下面我们就看一下在代码中是怎么实现的。
1、Mapper 接口的代理对象的创建
所有的 Mapper 接口都会被动态代理生成一个对象,这个对象每一次调用 sql 查询的时候都是通过
DefaultSqlSessionFactory 先生成一个 SqlSession 对象,然后调用这个 SqlSession 执行 Sql 的
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
// 省略相关代码
}
复制代码
在这里我们看到在 SqlSession 对象创建的时候,先创建了一个 Executor 对象
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
复制代码
可以看到在方法里面,如果设置了 cacheEnabled,则使用 CachingExecutor 再包装一遍先前创建好的 Executor,回顾我们在第四小结的内容,cacheEnabled 对应配置文件中的 mybatis.configuration.cache-enabled 配置,而 CachingExecutor 执行器里面也对查询方法进行了扩展,使得支持了二级缓存。
六、总结
1、Mybatis 默认支持一级缓存,实现方式是在 BaseExecutor 中,通过本地的缓存 PerpetualCache 缓存数据。
2、二级缓存默认不支持,实现方式是在 CachingExecutor 中,通过相关配置开启。如果 debug 看见 SQL 执行链中有 CachingExecutor,则表示开启了二级缓存。当然,要开启缓存还需要在 XML 中针对 SQL 配置 useCache 为 True。而如果配置了 flushCache 则表示会清空本地缓存(懒加载清空)。
评论