Mybatis一级、二级缓存

在MyBatis中,缓存机制是减少数据库访问次数、优化查询性能的重要功能。MyBatis 提供了两级缓存:一级缓存和二级缓存。

一级缓存(Local Cache)

一级缓存是基于 SqlSession 级别的,即每个 SqlSession 对象都有自己的一级缓存。它默认是开启的,不需要任何配置。

原理解析

一级缓存的实现是通过保存查询结果到一个本地的Map中实现的,它的作用域是同一个 SqlSession。当在同一个 SqlSession 中执行相同的查询时,会从这个Map中直接获取到查询结果。

关键实现在 BaseExecutor 中:

java 复制代码
public abstract class BaseExecutor implements Executor {
  private final PerpetualCache localCache = new PerpetualCache("LocalCache");
  
  // ... 省略其他代码 ...
  
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) {
    if (localCache.getObject(cacheKey) != null) {
      // 从本地缓存获取结果
      return localCache.getObject(cacheKey);
    }
    // 查询数据库
    return doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  }
}

代码演示

java 复制代码
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 第一次查询
  Blog blog1 = mapper.selectBlog(101);
  // 第二次相同的查询将会从一级缓存中获取数据
  Blog blog2 = mapper.selectBlog(101);
}

在以上代码中,第二次调用 selectBlog 方法时,并不会执行真正的SQL查询,而是直接从一级缓存中返回结果。

二级缓存(Global Cache)

二级缓存是基于 namespace 级别的,即映射的同一个mapper的不同实例之间共享这个缓存。

启用二级缓存

首先需要在 MyBatis 配置文件中启用二级缓存:

xml 复制代码
<settings>
  <!-- 开启全局缓存 -->
  <setting name="cacheEnabled" value="true"/>
</settings>

然后在需要使用二级缓存的 mapper xml 中配置 <cache/> 标签:

xml 复制代码
<mapper namespace="com.example.mapper.BlogMapper">
  <!-- 启用二级缓存 -->
  <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
</mapper>

原理解析

二级缓存的核心是 Cache 接口及其实现,如 PerpetualCacheLruCache 等。

在 MyBatis 的 CachingExecutor 类中使用了装饰器模式包装了 Executor 对象,以实现二级缓存的功能:

java 复制代码
public class CachingExecutor implements Executor {
  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  
  // ... 省略其他代码 ...
  
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        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);
  }
}

代码演示

java 复制代码
try (SqlSession session1 = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session1.getMapper(BlogMapper.class);
  Blog blog1 = mapper.selectBlog(101);
  session1.commit(); // 必须commit才能将结果放入二级缓存
}

try (SqlSession session2 = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session2.getMapper(BlogMapper.class);
  Blog blog2 = mapper.selectBlog(101); // 这里将会使用二级缓存的数据
}

在以上代码中,虽然session1session2是两个不同的SqlSession实例,但是它们共享相同的mapper命名空间下的二级缓存。

  • 一级缓存仅在一个SqlSession内部有效,可以通过SqlSession.clearCache()清空。
  • 二级缓存跨SqlSession有效,但需要显示配置,且必须提交事务后才会将数据存入缓存。
  • 缓存中存储的是数据的副本,而不是直接引用,这就意味着缓存中的数据可能与数据库中的数据有所不同。
  • 缓存配置可设置淘汰策略(eviction)、清理间隔(flushInterval)、缓存大小(size)以及是否只读(readOnly)。
  • 二级缓存应谨慎使用,特别是在有多个事务都可能修改同一数据时,因为它可能导致脏读、不可重复读和幻读。
  • 在使用二级缓存时,缓存对象必须实现Serializable接口,因为它们可能需要从内存中序列化到磁盘。

理解和正确使用MyBatis的缓存机制,可以显著提升应用程序的性能,但同时也需要注意保证数据的一致性。

相关推荐
一只爱打拳的程序猿18 分钟前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
假装我不帅2 小时前
asp.net framework从webform开始创建mvc项目
后端·asp.net·mvc
神仙别闹2 小时前
基于ASP.NET+SQL Server实现简单小说网站(包括PC版本和移动版本)
后端·asp.net
计算机-秋大田2 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
货拉拉技术3 小时前
货拉拉-实时对账系统(算盘平台)
后端
掘金酱3 小时前
✍【瓜分额外奖金】11月金石计划附加挑战赛-活动命题发布
人工智能·后端
代码之光_19803 小时前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi4 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
颜淡慕潇4 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决