13. 说说 MyBatis 的缓存机制?
大约 4 分钟
MyBatis 提供了一级缓存和二级缓存两种缓存机制,用于提高应用程序的性能,减少数据库的访问次数。缓存机制是 MyBatis 的一个重要特性,通过缓存可以在一定程度上减少对数据库的访问,从而提高查询性能。
1. 一级缓存(Local Cache)
作用范围:一级缓存是 MyBatis 默认开启的缓存机制,作用于 SqlSession
级别。
特点:
- 生命周期:一级缓存的生命周期与
SqlSession
相同。在同一个SqlSession
中执行多次相同的查询(条件相同),如果缓存中已经存在结果,则直接返回缓存中的结果,而不再向数据库发送查询请求。 - 存储方式:一级缓存是基于 PerpetualCache 和 HashMap 实现的,存储查询结果的键值对,键为 SQL 语句的字符串及其参数,值为查询结果。
- 失效条件:在
SqlSession
中,如果执行了INSERT
、UPDATE
、DELETE
操作,一级缓存会被清空,确保数据的一致性。另外,手动清空缓存或关闭SqlSession
也会使一级缓存失效。
示例:
SqlSession session = sqlSessionFactory.openSession();
User user1 = session.selectOne("selectUserById", 1); // 从数据库查询
User user2 = session.selectOne("selectUserById", 1); // 从一级缓存中获取结果
2. 二级缓存(Global Cache)
作用范围:二级缓存是 MyBatis 的全局缓存机制,作用于 Mapper
映射器级别。即同一个 Mapper
映射器中的查询结果可以被不同的 SqlSession
实例共享。
特点:
- 默认关闭:二级缓存默认是关闭的,需要在配置文件或
Mapper
文件中显式开启。 - 跨
SqlSession
:二级缓存是跨SqlSession
的,多个SqlSession
可以共享同一个Mapper
中的缓存数据。 - 存储方式:二级缓存也是基于 PerpetualCache 实现的,并且需要将缓存对象序列化,因此缓存对象必须实现
Serializable
接口。 - 失效条件:当执行
INSERT
、UPDATE
、DELETE
等修改操作时,二级缓存会被清空。同时,事务提交后,查询结果才会写入二级缓存。
启用二级缓存:
在 MyBatis 全局配置文件中启用二级缓存:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
在具体的
Mapper
文件中配置二级缓存:<mapper namespace="com.example.mapper.UserMapper"> <!-- 启用二级缓存 --> <cache /> <select id="selectUserById" resultType="User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
示例:
// 第一次查询,结果存入二级缓存
try (SqlSession session1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1);
}
// 第二次查询,不同的 SqlSession 实例,结果从二级缓存中获取
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectUserById(1);
}
3. 一级缓存与二级缓存的比较
特性 | 一级缓存 | 二级缓存 |
---|---|---|
作用范围 | SqlSession 级别 | Mapper 映射器级别,跨 SqlSession |
默认状态 | 默认开启 | 默认关闭 |
缓存生命周期 | 与 SqlSession 相同 | 与 Mapper 相同 |
缓存失效 | SqlSession 执行写操作时清空 | Mapper 执行写操作时清空 |
缓存共享 | 不同的 SqlSession 之间不共享 | 不同的 SqlSession 之间共享 |
4. 配置和管理二级缓存
MyBatis 提供了多种方式来配置和管理二级缓存:
- 缓存策略:可以通过
<cache>
标签的eviction
属性配置缓存回收策略。默认使用LRU
(最近最少使用),其他选项包括FIFO
(先进先出)、SOFT
(软引用)、WEAK
(弱引用)。 - 刷新间隔:通过
flushInterval
属性配置缓存的刷新间隔。默认情况下不会自动刷新,可以设置为毫秒级的刷新间隔。 - 缓存大小:通过
size
属性设置缓存的最大对象数量。 - 只读模式:通过
readOnly
属性设置缓存是否为只读模式。只读模式下,缓存中的对象在缓存期间不可修改,适合提高并发性能。
示例配置:
<cache
eviction="FIFO"
flushInterval="60000" <!-- 缓存60秒刷新一次 -->
size="512" <!-- 缓存中最多保存512个对象 -->
readOnly="true" <!-- 只读缓存,提升并发性能 -->
/>
5. 使用注意事项
- 事务管理:二级缓存和事务管理紧密相关,只有在事务提交后,查询结果才会存入二级缓存。如果事务回滚,缓存不会更新。
- 对象序列化:由于二级缓存需要将对象序列化,所有被缓存的对象必须实现
Serializable
接口。 - 一致性问题:二级缓存会在更新数据时失效,但在高并发情况下,可能会有短暂的缓存不一致问题,需要根据实际情况权衡缓存带来的性能提升与一致性问题。
6. 总结
- 一级缓存:每个
SqlSession
维护自己的一级缓存,默认开启,不同SqlSession
之间不共享。适用于单个会话内重复查询的场景。 - 二级缓存:二级缓存是跨
SqlSession
的全局缓存,默认关闭,需要手动开启和配置。适用于多次会话之间需要共享查询结果的场景。
通过合理配置 MyBatis 的一级和二级缓存,可以显著提升应用程序的性能,特别是在读多写少的场景中,缓存机制可以有效减少数据库访问次数,提升查询效率。