一级缓存机制
MyBatis 一级缓存是 SqlSession 级别的本地缓存,默认开启。在同一 SqlSession 中执行相同查询时,第二次查询会直接从缓存返回结果,不再访问数据库。
一级缓存原理
一级缓存基于 PerpetualCache 实现,底层使用 HashMap 存储数据。每个 SqlSession 持有独立的缓存实例,不同 SqlSession 之间缓存不共享。
Java
// MyBatis 内部结构
public class BaseExecutor implements Executor {
// 一级缓存实例
protected PerpetualCache localCache;
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler) {
// 生成缓存 Key
CacheKey key = createCacheKey(ms, parameter, rowBounds);
// 先从本地缓存获取
List<E> list = (List<E>) localCache.getObject(key);
if (list != null) {
return list; // 缓存命中,直接返回
}
// 缓存未命中,查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key);
return list;
}
}
缓存 Key 生成规则
缓存 Key 由以下因素共同决定:
| 因素 | 说明 |
|---|---|
MappedStatement.id | SQL 语句的唯一标识 |
RowBounds | 分页偏移量 |
| SQL 语句本身 | 包含参数的 SQL 文本 |
| 参数值 | 传入的实际参数值 |
只有上述因素完全一致时,才会命中缓存。
缓存命中条件
要命中一级缓存,必须同时满足以下条件:
- 同一个 SqlSession:不同 Session 缓存隔离
- 相同的 SQL 语句:
MappedStatement.id一致 - 相同的参数值:参数值完全相等
- 相同的 RowBounds:分页条件一致
- 中间没有执行写操作:INSERT/UPDATE/DELETE 会清空缓存
- 没有手动调用 clearCache():主动清理缓存
命中示例
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询,访问数据库
User user1 = mapper.selectById(1);
System.out.println("Query 1: " + user1);
// 第二次相同查询,命中缓存,不访问数据库
User user2 = mapper.selectById(1);
System.out.println("Query 2: " + user2);
// 两次查询返回同一对象引用
System.out.println(user1 == user2); // true
session.close();
不命中示例
Java
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
// 不同 SqlSession,缓存不共享
User user1 = mapper1.selectById(1); // 访问数据库
User user2 = mapper2.selectById(1); // 访问数据库
System.out.println(user1 == user2); // false
session1.close();
session2.close();
缓存失效场景
一级缓会在以下场景中自动清空:
1. 执行写操作后自动清空
任何 INSERT/UPDATE/DELETE 操作都会清空一级缓存:
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询,访问数据库
User user = mapper.selectById(1);
// 执行更新操作,清空一级缓存
mapper.updateUser(user);
session.commit();
// 再次查询,缓存已失效,重新访问数据库
User user2 = mapper.selectById(1);
System.out.println(user == user2); // false
| 写操作类型 | 清空缓存 | 说明 |
|---|---|---|
| INSERT | 是 | 新增数据后缓存失效 |
| UPDATE | 是 | 更新数据后缓存失效 |
| DELETE | 是 | 删除数据后缓存失效 |
| COMMIT | 是 | 提交事务时清空缓存 |
2. 手动清空缓存
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 查询数据
User user = mapper.selectById(1);
// 手动清空一级缓存
session.clearCache();
// 再次查询,重新访问数据库
User user2 = mapper.selectById(1);
System.out.println(user == user2); // false
3. 事务回滚
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
try {
User user = mapper.selectById(1);
// 执行更新
mapper.updateUser(user);
// 模拟异常
if (true) throw new RuntimeException("test");
session.commit();
} catch (Exception e) {
// 回滚事务,清空一级缓存
session.rollback();
}
// 回滚后缓存已失效
User user2 = mapper.selectById(1);
4. Session 关闭
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);
// 关闭 Session,一级缓存被销毁
session.close();
// 新 Session,全新缓存
SqlSession newSession = sqlSessionFactory.openSession();
UserMapper newMapper = newSession.getMapper(UserMapper.class);
User user2 = newMapper.selectById(1);
localCacheScope 配置
MyBatis 提供 localCacheScope 配置项,控制一级缓存的作用域:
| 配置值 | 说明 | 适用场景 |
|---|---|---|
| SESSION | 默认值,Session 级别缓存 | 一般业务场景 |
| STATEMENT | 语句级别,每次查询后清空 | 需要实时数据的场景 |
SESSION 级别(默认)
XML
<!-- mybatis-config.xml -->
<settings>
<!-- 默认值为 SESSION,可省略 -->
<setting name="localCacheScope" value="SESSION"/>
</settings>
SESSION 级别下,同一 SqlSession 内多次相同查询命中缓存:
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 查询 3 次
User u1 = mapper.selectById(1); // 访问数据库
User u2 = mapper.selectById(1); // 命中缓存
User u3 = mapper.selectById(1); // 命中缓存
STATEMENT 级别
XML
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
STATEMENT 级别下,每次查询后自动清空缓存:
Java
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 查询 3 次
User u1 = mapper.selectById(1); // 访问数据库
User u2 = mapper.selectById(1); // 访问数据库(STATEMENT 模式下已清空)
User u3 = mapper.selectById(1); // 访问数据库
STATEMENT 级别适用于对数据实时性要求高的场景,如金融交易、库存管理等。
缓存与事务的关系
一级缓存的生命周期与事务紧密相关:
Java
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询
User user = mapper.selectById(1);
// 更新操作
user.setUsername("newName");
mapper.updateUser(user);
// 提交事务,同时清空一级缓存
session.commit();
// 查询需要重新访问数据库
User user2 = mapper.selectById(1);
} catch (Exception e) {
// 回滚事务,同时清空一级缓存
session.rollback();
} finally {
session.close();
}
一级缓存使用场景与限制
| 场景 | 是否适用 | 说明 |
|---|---|---|
| 同 Session 多次相同查询 | 适用 | 典型缓存命中场景 |
| 读写混合频繁 | 不适用 | 每次写操作清空缓存 |
| 多 Session 并发 | 不适用 | 一级缓存不跨 Session 共享 |
| 分布式环境 | 不适用 | 一级缓存是本地缓存,多实例不共享 |
| 需要实时数据 | 不适用 | 建议使用 STATEMENT 级别 |
要点总结
- 一级缓存是 SqlSession 级别的本地缓存,底层基于 HashMap 实现
- 缓存命中条件:同一 SqlSession、相同 SQL、相同参数、相同 RowBounds、无写操作
- 写操作(INSERT/UPDATE/DELETE)和 commit/rollback/clearCache 都会清空一级缓存
localCacheScope可配置为 SESSION(默认)或 STATEMENT 级别- SESSION 级别下缓存持续存在,STATEMENT 级别下每次查询后清空
- 一级缓存不跨 Session 共享,不适合分布式环境
存放路径:D:\git2\jwdev\articles\MYBATIS\进阶\缓存机制\一级缓存机制.md
📝 发现内容有误?点击此处直接编辑