深入解析,MyBatis的一二级缓存
引言
在数据密集型应用中,缓存是提升性能的关键技术之一。MyBatis作为Java生态中流行的ORM框架,提供了一套完善的缓存机制,包括一级缓存和二级缓存。本文将深入解析MyBatis的二级缓存,通过实际代码示例和场景分析,帮助你理解这两种缓存的区别,以及如何在项目中合理使用它们。
MyBatis缓存基础
MyBatis的缓存机制旨在减少数据库访问次数,提高应用性能。它通过将查询结果存储在内存中,在后续相同查询时直接返回缓存数据,避免重复访问数据库。MyBatis提供了两级缓存:
- 一级缓存(Local Cache):SqlSession级别的缓存,默认开启
- 二级缓存(Second Level Cache):Mapper级别的缓存,需要手动配置
这两种缓存在作用范围、生命周期和使用场景上有显著差异,接下来我们将详细探讨。
一级缓存详解
一级缓存是MyBatis的默认缓存,它的作用范围仅限于单个SqlSession内部。每个SqlSession都有自己独立的缓存空间,不同SqlSession之间的缓存数据互不可见。
一级缓存的工作原理
- 当执行查询时,MyBatis首先检查一级缓存中是否存在对应的数据
- 如果缓存命中,直接返回缓存数据,不访问数据库
- 如果缓存未命中,则访问数据库并将查询结果存入一级缓存
- 当执行更新、删除、插入操作,或调用
clearCache()
方法时,一级缓存会被清空
一级缓存的生命周期
一级缓存随SqlSession的创建而创建,随SqlSession的关闭或提交而销毁。它是一种短暂的、会话级的缓存,主要用于优化单次会话内的重复查询。
二级缓存详解
二级缓存是MyBatis提供的跨SqlSession的缓存机制,它的作用范围是namespace(即Mapper接口)级别。同一namespace下的所有SqlSession共享二级缓存数据。
二级缓存的启用方式
要启用二级缓存,需要满足以下条件:
- 在MyBatis配置文件中设置
cacheEnabled
为true(默认值) - 在Mapper XML文件中添加
<cache/>
标签 - 查询语句设置
useCache="true"
(默认值) - 会话需要提交或关闭后,查询结果才会被二级缓存存储
二级缓存的工作原理
- 查询时,MyBatis先检查二级缓存,再检查一级缓存
- 如果二级缓存命中,直接返回缓存数据
- 如果二级缓存未命中,则继续检查一级缓存
- 如果一级缓存也未命中,则访问数据库
- 查询结果会先存入一级缓存,当会话提交或关闭时,数据才会被写入二级缓存
- 当执行更新操作并提交事务时,会清空相关namespace的二级缓存
代码示例分析
让我们通过一个具体的UserMapper.xml
示例来理解MyBatis缓存机制:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 手动开启二级缓存 -->
<cache/>
<select id="selectUserById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
<update id="updateUser" parameterType="User">
UPDATE user SET name = #{name} WHERE id = #{id}
</update>
</mapper>
在这个配置中:
<cache/>
标签开启了UserMapper命名空间的二级缓存useCache="true"
表示selectUserById查询结果会被缓存- 没有特别配置的情况下,updateUser操作会导致该命名空间的二级缓存失效
一级缓存行为分析
让我们看看一级缓存在实际场景中的行为:
// 场景1:同一个 SqlSession 内重复查询同一数据
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询:访问数据库,结果存入一级缓存
User user1 = mapper.selectUserById(1);
// 第二次查询:直接从一级缓存获取,不访问数据库
User user2 = mapper.selectUserById(1);
// 执行更新操作:清空一级缓存
mapper.updateUser(new User(1, "NewName"));
// 第三次查询:更新后一级缓存失效,再次访问数据库
User user3 = mapper.selectUserById(1);
}
结果分析:

- 第一次查询会访问数据库,并将结果存入一级缓存
- 第二次查询直接从一级缓存获取数据,不会产生数据库查询
- 执行更新操作后,一级缓存会被自动清空
- 第三次查询因缓存已失效,会再次访问数据库
这种机制确保了在单个会话内的数据一致性,避免脏读问题。
二级缓存行为分析
接下来,让我们分析二级缓存在跨会话场景中的行为:
// 场景2:跨 SqlSession 查询同一数据(需开启二级缓存)
// 第一个 SqlSession
try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次查询:访问数据库,结果存入一级缓存
User user1 = mapper1.selectUserById(1);
sqlSession1.commit(); // 提交后数据才会进入二级缓存
}
// 第二个 SqlSession
try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次查询:直接命中二级缓存(不访问数据库)
User user2 = mapper2.selectUserById(1);
}
// 第三个 SqlSession 执行更新
try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
mapper3.updateUser(new User(1, "AnotherName"));
sqlSession3.commit(); // 提交后二级缓存失效
}
// 第四个 SqlSession 再次查询
try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {
UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class);
// 第三次查询:更新导致二级缓存失效,重新访问数据库
User user3 = mapper4.selectUserById(1);
}
结果分析:
- 第一个SqlSession执行查询并提交事务,数据被存入二级缓存
- 第二个SqlSession直接从二级缓存获取数据,无需访问数据库
- 第三个SqlSession执行更新操作并提交,导致该命名空间的二级缓存失效
- 第四个SqlSession再次查询时,因二级缓存已失效,需要重新访问数据库
这种机制使得跨会话访问相同数据时,能有效减少数据库交互,同时保证数据的一致性。
一级缓存与二级缓存的比较
特性 | 一级缓存 | 二级缓存 |
---|---|---|
作用域 | 单个 SqlSession 内部 |
跨 SqlSession (同一 namespace 下共享) |
生命周期 | 随 SqlSession 关闭或提交而销毁 |
持久化,直到显式清除或配置的过期策略触发 |
数据共享 | 仅当前会话可见 | 所有会话共享 |
失效条件 | 执行更新操作或调用 clearCache() |
同 namespace 的更新操作(需提交事务) |
配置方式 | 默认开启,无需配置 | 需手动添加 <cache/> 标签 |
适用场景 | 单次会话内高频重复查询 | 跨会话的全局静态数据(如配置表、低频更新数据) |
实现复杂度 | 简单,自动管理 | 需要考虑序列化、缓存策略等因素 |
数据一致性 | 会话内一致性高 | 需要谨慎处理更新操作的影响 |
使用二级缓存的注意事项
1. 实体类序列化要求
二级缓存中的数据需要在不同的JVM进程间传输,因此缓存的对象必须实现Serializable
接口:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
// getters and setters
}
如果实体类未实现此接口,使用二级缓存时MyBatis会抛出异常。
2. 事务提交与二级缓存
二级缓存的数据只有在SqlSession提交事务后才会被真正存入缓存。这是因为MyBatis需要确保只有成功执行的查询结果才被缓存,避免存储不完整或错误的数据。
3. 适用场景选择
二级缓存适合用于:
- 读多写少的数据
- 变更频率低的参考数据
- 全局配置信息
不适合用于:
- 高频更新的数据
- 对实时性要求高的核心业务数据
- 涉及多表关联的复杂查询
4. 分布式环境问题
在分布式系统中,MyBatis的二级缓存可能导致数据不一致问题,因为默认的二级缓存是进程级的,不同服务器节点之间的缓存数据不会自动同步。解决方案包括:
- 使用Redis等分布式缓存替代MyBatis二级缓存
- 配置MyBatis集成EhCache、Memcached等支持分布式的缓存实现
- 在缓存配置中设置较短的过期时间,减少不一致窗口期
5. 高级缓存配置
MyBatis提供了丰富的缓存配置选项:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="false"/>
参数说明:
eviction
:缓存清除策略(LRU、FIFO、SOFT、WEAK)flushInterval
:自动刷新时间(毫秒)size
:缓存引用数量readOnly
:是否只读(true可提高性能但无法修改缓存对象)
总结
MyBatis的二级缓存机制为我们提供了强大的性能优化工具,正确使用可以显著减少数据库访问,提升应用响应速度。
- 一级缓存适用于单会话内的查询优化,无需配置,自动管理
- 二级缓存适用于跨会话的数据共享,需要手动配置,并注意数据一致性问题
在实际应用中,应根据业务特性、数据变更频率和一致性要求来选择合适的缓存策略。对于分布式环境,可能需要考虑使用专业的分布式缓存解决方案替代MyBatis原生的二级缓存。
正确理解和使用MyBatis的缓存机制,将帮助你在保证数据一致性的同时,最大限度地提升应用性能。
觉得有用的话可以点点赞 (/ω\),支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每周都会不定时更新哦 >人< 。