一、缓存的意义
我们都知道程序中缓存的意义是为了减少一些重复的操作,而把结果存储起来重复利用。同样的,对于数据库操作来说,缓存结果可以减少与数据库的交互,提升程序性能。
二、缓存的作用域
对于数据库查询结果的缓存,缓存的结构自然就是《 执行的 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 则表示会清空本地缓存(懒加载清空)。
评论