写点什么

Mybatis 中的一二级缓存

作者:捡对象的cy
  • 2023-08-20
    广东
  • 本文字数:2847 字

    阅读完需:约 9 分钟

一、缓存的意义

我们都知道程序中缓存的意义是为了减少一些重复的操作,而把结果存储起来重复利用。同样的,对于数据库操作来说,缓存结果可以减少与数据库的交互,提升程序性能。


二、缓存的作用域

对于数据库查询结果的缓存,缓存的结构自然就是《 执行的 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 则表示会清空本地缓存(懒加载清空)。


发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2021-04-27 加入

还未添加个人简介

评论

发布
暂无评论
Mybatis中的一二级缓存_mybatis缓存_捡对象的cy_InfoQ写作社区