SpringBoot+Mybatis一级缓存和二级缓存详解
Spring Boot + MyBatis 一级缓存和二级缓存详解
本文主要介绍在 Spring Boot 项目中如何使用 MyBatis 的一级缓存和二级缓存。为了演示方便,本文的数据库采用 H2 内存数据库,数据库连接池默认使用 Spring Boot 2.x 自带的 HikariCP。
正确使用 MyBatis 缓存可以有效减少多余的数据库查询操作,节约 I/O 资源。接下来我们从实践出发,看一看 MyBatis 的一级缓存、二级缓存如何使用。相关代码请查阅:https://github.com/zhengxl5566/springboot-demo.git
1. 概念介绍
- 什么是一级缓存
在日常开发过程中,经常会有相同的 SQL 执行多次查询的情况。MyBatis 提供了一级缓存来优化这些查询,避免多次请求数据库。
一级缓存在 MyBatis 中默认是开启的,并且是 Session 级别,它的作用域为一次SqlSession会话。 - 什么是二级缓存
相对于一级缓存,二级缓存的作用域更广泛。它不止局限于一个SqlSession,可以在多个SqlSession之间共享。事实上,它的作用域是 Namespace。
MyBatis 的二级缓存全局开关默认是开启的,但由于它的作用域是 Namespace,所以还需要在mapper.xml中配置<cache>标签才能生效。 - 缓存的优先级
通过 MyBatis 发起的查询,查找顺序为:二级缓存 -> 一级缓存 -> 数据库。其中任何一个环节查到不为空的数据,都将直接返回结果。 - 缓存失效
当在一个缓存作用域中发生了update、insert、delete动作后,将会触发缓存失效。下一次查询将命中数据库,从而保证不会查到脏数据。
2. 代码演示
2.1 一级缓存
默认情况下,MyBatis 开启并使用了一级缓存。
单元测试用例:
/**
* 开启事务,测试一级缓存效果
* 缓存命中顺序:二级缓存 ---> 一级缓存 ---> 数据库
**/
@Test
@Transactional(rollbackFor = Throwable.class)
public void testFistCache(){
// 第一次查询,缓存到一级缓存
userMapper.selectById(1);
// 第二次查询,直接读取一级缓存
userMapper.selectById(1);
}执行结果:

可以看到,虽然进行了两次查询,但最终只请求了一次数据库。第二次查询命中了一级缓存,直接返回了数据。
这里有两点需要说明:
- 为什么开启事务?
由于使用了数据库连接池(HikariCP),默认每次查询完之后自动commit,这就导致两次查询使用的不是同一个SqlSession。根据一级缓存的原理,它将永远不会生效。
当我们开启了事务,两次查询都在同一个SqlSession中,从而让第二次查询命中了一级缓存。读者可以自行关闭事务验证此结论。 - 两种一级缓存模式
一级缓存的作用域有两种:SESSION(默认)和STATEMENT。可通过设置local-cache-scope的值来切换,默认为SESSION。
二者的区别在于SESSION会将缓存作用于同一个SqlSession,而STATEMENT仅针对一次查询。所以,local-cache-scope: STATEMENT可以理解为关闭一级缓存。
2.2 二级缓存
默认情况下,MyBatis 打开了二级缓存的全局开关,但它并未立即生效。因为二级缓存的作用域是 Namespace,所以还需要在 Mapper.xml 文件中配置一下才能使二级缓存生效。
- 单表二级缓存
下面对userMapper.xml配置一下,让其二级缓存生效,只需加入<cache>标签即可。
<cache></cache>单元测试用例:
/**
* 测试二级缓存效果
* 需要 *Mapper.xml 开启二级缓存
**/
@Test
public void testSecondCache(){
userMapper.selectById(1);
userMapper.selectById(1);
}执行结果:

这里可以看到,第二次查询直接命中了缓存,日志还打印了该缓存的命中率。读者可以自行关闭二级缓存查看效果,通过注掉对应 mapper.xml 的 <cache> 标签,或者设置 cache-enabled: false 均可。
多表联查二级缓存
接下来演示多表联查的二级缓存,user表left join user_order表on user.id = user_order.user_id。我们考虑这样一种情况:该联查执行两次,第二次联查前更新
user_order表。如果只使用<cache>配置,将会查不到更新的user_order数据,因为两个mapper.xml的作用域不同。要想合到一个作用域,就需要用到<cache-ref>。userOrderMapper.xml:
<cache></cache>`userMapper.xml`:
<cache-ref namespace="com.zhengxl.mybatiscache.mapper.UserOrderMapper"/>单元测试用例:
/**
* 测试多表联查的二级缓存效果
* 需要 *Mapper.xml 设定引用空间
**/
@Test
public void testJoin(){
System.out.println(userMapper.selectJoin());
System.out.println(userMapper.selectJoin());
UserOrder userOrder = new UserOrder();
userOrder.setGoodName("myGoods");
userOrder.setUserId(1);
userOrderMapper.saveOrder(userOrder);
System.out.println(userMapper.selectJoin());
}执行结果:

首先查询了两次 user 表,第二次命中二级缓存;然后更新 user_order 表,使缓存失效;第三次查询时命中数据库。
总结与建议
综上,MyBatis 的单机缓存机制介绍完毕,读者可以自行下载样例工程验证。
- 一级缓存:MyBatis 默认的 Session 级别一级缓存,由于 Spring Boot 中默认使用了 HikariCP,所以基本没用,需要开启事务才有用。但一级缓存作用域仅限同一
SqlSession内,无法感知到其他SqlSession的增删改,所以极易产生脏数据。 - 二级缓存:可通过
<cache-ref>让多个mapper.xml共享同一 Namespace,从而实现缓存共享,但多表联查时配置略微繁琐。
生产环境建议: 将一级缓存设置为 STATEMENT 级别(即关闭一级缓存),如果有必要,可以开启二级缓存。
注意事项
分布式部署警告:如果应用是分布式部署,由于二级缓存存储在本地内存,必然导致不同节点间查询出脏数据。所以,分布式部署的应用不建议开启 MyBatis 二级缓存,建议使用 Redis 等集中式缓存方案。
参考资料
说明:本文基于 Spring Boot 2.x 及 MyBatis 默认配置编写,适用于单机或非分布式场景。在微服务或分布式架构中,请谨慎评估二级缓存的使用。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/springbootmybatis-yi-ji-huan-cun-he-er-ji-huan-cun-xiang-jie.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。