引入:
在高并发系统中,缓存是提升查询性能的核心手段之一。MyBatis 内置了两级缓存机制,能有效减少数据库交互次数,降低系统开销。本文将从"缓存基础"到"MyBatis 一/二级缓存的实战配置、失效场景",全面解析 MyBatis 缓存的核心逻辑。
一、缓存基础:是什么?为什么用?
1.1 什么是缓存?
缓存是存储在内存中的数据,将用户高频访问的数据暂存于内存,避免每次查询都从磁盘(数据库)读取,从而提升查询效率。
1.2 为什么使用缓存?
- 减少与数据库的交互次数,降低数据库压力;
- 降低系统 IO 开销,提升接口响应速度;
- 解决高并发场景下的性能瓶颈。
1.3 什么样的数据适合缓存?
高频查询 + 低频修改的数据(如字典表、配置信息等),不适合缓存"实时性要求高、频繁修改"的数据(如订单状态)。
二、MyBatis 缓存体系:一级缓存 + 二级缓存
MyBatis 默认提供两级缓存:
- 一级缓存:SqlSession 级别(默认开启);
- 二级缓存:SqlSessionFactory 级别(需手动配置)。
2.1 一级缓存(本地缓存)
2.1.1 核心概念
一级缓存是 SqlSession 级别的缓存:同一次 SqlSession 会话中,查询到的数据会被存入本地缓存;后续相同查询会直接从缓存获取,无需访问数据库。
2.1.2 实战测试一级缓存
```java
java
@Test
public void testFirstLevelCache() throws IOException {
// 1. 加载配置,创建SqlSessionFactory
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 2. 创建SqlSession(同一会话)
SqlSession session = factory.openSession();
UserDao mapper = session.getMapper(UserDao.class);
// 3. 第一次查询(访问数据库)
User user1 = mapper.findById(1);
System.out.println(user1);
// 4. 第二次查询(从一级缓存获取)
User user2 = mapper.findById(1);
System.out.println(user2);
// 5. 验证缓存(对象地址相同)
System.out.println("缓存命中:" + (user1 == user2)); // 输出true
// 6. 释放资源
session.close();
in.close();
}
```
2.1.3 一级缓存失效的4种场景
- SqlSession 不同:不同 SqlSession 属于不同缓存空间;
- 查询条件不同:缓存按"查询语句 + 参数"唯一标识,条件不同则缓存不命中;
- 两次查询间执行增删改操作:增删改会触发缓存清空(保证数据一致性);

- 手动清除缓存:调用 session.clearCache() 主动清空缓存。
2.2 二级缓存(全局缓存)
2.2.1 核心概念
二级缓存是 SqlSessionFactory 级别的缓存:同一 SqlSessionFactory 创建的 SqlSession,查询数据会存入二级缓存;跨 SqlSession 共享缓存数据。
2.2.2 开启二级缓存的4个条件
- 全局配置开启缓存:在 SqlMapConfig.xml 中开启全局缓存开关;

- Mapper 映射文件声明缓存:在对应 Mapper.xml 中配置 ;

- 实体类实现序列化:缓存数据需序列化存储(防止内存溢出);

- 二级缓存必须在SqlSession关闭(一级缓存)或提交(二级缓存)之后有效

2.2.3 实战配置二级缓存
步骤1:全局配置开启缓存(SqlMapConfig.xml)
```xml
XML
<!‐‐ 开启二级缓存 ‐‐>
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
```
步骤2:Mapper 映射文件声明缓存(UserDao.xml)
```xml
XML
<!--使用二级缓存-->
<cache/>
```
步骤3:实体类实现序列化
```java
java
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// get set方法 .....
}
```
步骤4:测试二级缓存
```java
java
@Test
public void testSecondLevelCache() throws IOException {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 第一个SqlSession(查询后关闭,将数据写入二级缓存)
SqlSession session1 = factory.openSession();
UserDao mapper1 = session1.getMapper(UserDao.class);
User user1 = mapper1.findById(1);
session1.close(); // 关闭后写入二级缓存
// 第二个SqlSession(从二级缓存获取)
SqlSession session2 = factory.openSession();
UserDao mapper2 = session2.getMapper(UserDao.class);
User user2 = mapper2.findById(1);
session2.close();
// 验证缓存(数据相同,但对象地址不同)
System.out.println("数据一致:" + user1.equals(user2)); // true
System.out.println("对象地址不同:" + (user1 == user2)); // false
}
```
2.2.4 二级缓存的重要结论
二级缓存存储的是**数据的序列化拷贝**,而非对象本身,因此两次查询的对象地址不同,但数据内容一致。
2.2.5 二级缓存的参数配置
`` 标签支持自定义缓存策略,常用参数:
```xml
XML
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true" />
```
三、MyBatis 缓存查询顺序
MyBatis 会按以下顺序查询缓存:
- 先查二级缓存(全局共享,优先命中);
- 二级缓存未命中 → 查一级缓存(当前 SqlSession 缓存);
- 一级缓存未命中 → 访问数据库;
- 数据库查询结果 → 存入一级缓存;
- SqlSession 关闭/提交 → 一级缓存数据写入二级缓存。
四、总结:MyBatis 缓存的最佳实践
- 一级缓存:默认开启,无需额外配置,适用于"短会话、高频重复查询"场景;
- 二级缓存:适用于"跨会话共享数据"场景,需注意"序列化、缓存失效规则";
- 缓存失效:增删改操作会清空对应缓存,保证数据一致性;
- 生产建议:若系统已使用 Redis 等分布式缓存,可关闭 MyBatis 二级缓存,避免缓存层级混乱。
MyBatis 缓存是提升查询性能的轻量方案,掌握其核心逻辑与失效场景,能帮助你在实际项目中合理利用缓存,避免"缓存穿透、缓存脏数据"等问题。