目录
前言
缓存就是指存在内存中的临时数据,能够减少和数据库交互的次数,提高效率。
将相同查询条件的sql语句执行一遍后得到的结果存在内存或者某种缓存介质中,当下次遇到相同的查询sql时候,不用先执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;
关于mybatis的更多介绍,可参考:
mybatis缓存包括:
● ⼀级缓存:将查询到的数据存储到SqlSession中。范围比较小,默认开启一级缓存。
● ⼆级缓存:范围比较大,针对于整个数据库级别的, 需要在 setting 全局参数中配置开启二级缓存。
⚠️注意:缓存只针对于DQL语句,也就是缓存机制只对应select语句。
1、MyBatis的缓存机制
1.1、定义
一级缓存是属于SqlSession对象,每次commit/close会被清空,而二级缓存可以被持久化和长期复用。
MyBatis内部的查询操作是先查namespace Cache(二级缓存),如果没找到,再查本地localcache(SqlSession)缓存。
1.2、分类
1. 一级缓存
SqlSession级别缓存,默认开启。
- 范围:同一个SqlSession内,相同的查询语句和参数,第二次查询时不会再去数据库,而是直接从缓存返回结果。
- 失效条件:增删改操作(flush)、手动清理缓存、切换了SqlSession、查询条件不一样。
- 线程隔离:不同SqlSession之间互不影响。
2. 二级缓存
Mapper级别/跨SqlSession的缓存,默认关闭。
- 范围:同一个Mapper命名空间下,不同SqlSession可以共享缓存。
- 失效条件:本Mapper下有更新操作或手动清理缓存。
- 生命周期:依赖MyBatis全局配置和Mapper开启了二级缓存;
- 持久化:可指定缓存实现(如Ehcache、Redis)。
1.3、缓存查询流程
用户A用SqlSessionA查过某条数据,执行commit或close,这条数据同步进了二级缓存。
用户B用SqlSessionB查询同一条数据,先查二级缓存能直接命中,无需等到A的SqlSession存活时才能复用,最大化缓存效率。
- 查询时优先通过MappedStatement.namespace查找对应的全局 cache 对象(即二级缓存)。
- 未命中,才查SqlSession中的 localCache(一级缓存)。
// org.apache.ibatis.executor.CacheExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, ...) {
Cache cache = ms.getCache();
if (cache != null) {
// 查二级缓存
...
}
// 一级缓存逻辑
...
}
MyBatis 之所以先查二级缓存、再查一级缓存,是因为二级缓存范围更广、复用率更高。如果先查一级缓存会导致大量数据无法被复用,降低缓存命中率。
先查全局(namespace)缓存有利于提升性能和一致性,这也是MyBatis一致性与性能设计的体现。
1.4、缓存参数Cache
1、flushInterval(刷新间隔)
可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
2、size(引用数目)
可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
3、readOnly(只读)
属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。
可用的收回策略有,默认的是 LRU:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
2、一级缓存
2.1、定义
一级缓存区域是根据 SqlSession 为单位划分的。 每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为 从查询出来映射生成的 java 对象 sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域。
2.2、流程图
如下图所示:
2.3、实现
假设有如下Mapper:
public interface UserMapper {
User selectUserById(int id);
}
代码演示:
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询,发送SQL
User user1 = mapper.selectUserById(1);
// 第二次相同条件查询,不发送SQL,直接从(一级)缓存返回
User user2 = mapper.selectUserById(1);
System.out.println(user1 == user2); // true(同个对象)
session.close();
注意:
- 换成不同的SqlSession,不共享一级缓存。
- 在同一个SqlSession内,若执行了insert
/
update/
delete,缓存会清空。delete
3、二级缓存
3.1、定义
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
3.2、整体设计
MyBatis的缓存机制整体设计以及二级缓存的工作模式。
如下:
3.3、实现
1. 开启全局二级缓存功能
关于mybatis-config.xml如下:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2. Mapper.xml中
开启二级缓存
<mapper namespace="com.example.dao.UserMapper">
<cache/> <!-- 一行即可开启这个mapper的二级缓存 -->
<select id="selectUserById" resultType="User">
SELECT id, name FROM user WHERE id = #{id}
</select>
</mapper>
3. 代码示例
// 会话1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);
// 第一次查询,查数据库
User user1 = mapper1.selectUserById(1);
// 一级缓存存在session1中
session1.close(); // 提交一级缓存到二级缓存
// 会话2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
// 第二次查询,先查二级缓存,如果有则不会去数据库
User user2 = mapper2.selectUserById(1);
session2.close();
System.out.println(user1 == user2); // false(对象地址不同,但数据是一样的)
常见注意点
- 二级缓存存储是序列化的数据(所以对象不是同一引用)。
- 任何insert
/
update/
delete操作会刷新相关 namespace 的二级缓存。 - 查询参数、SQL变化都可能被认为是不同key,不会命中。
4、二级缓存自定义实现
可以指定缓存实现,如:
<cache type="org.mybatis.caches.ehcache.EhcacheCache"
eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
也可自己实现 Cache 接口接入Redis等。
5、总结
- 一级缓存:SqlSession 级,默认开启,生命周期短,线程不共享;
- 二级缓存:Mapper(namespace)级,需配置开启,多个会话共享,默认内存存储,支持第三方缓存实现。
- 实际应用建议:对于高频读多写少业务可考虑开启二级缓存,并做好缓存失效设计;写多读少场景/强一致要求则慎用二级缓存。
参考文章: