MyBatis 提供了两种主要的内置缓存机制来提高查询性能,减少对数据库的直接访问:一级缓存 (Level 1 Cache) 和 二级缓存 (Level 2 Cache)。
1. 一级缓存 (Local Cache / SqlSession Cache)
- 定义: 一级缓存是 SqlSession 级别的缓存。它在 MyBatis 中是默认开启且无法关闭的(但可以通过设置
localCacheScope=STATEMENT使其作用范围仅限于语句执行期间,效果接近关闭,但通常不推荐)。 - 作用域: 它的生命周期与
SqlSession相同。当SqlSession被创建时,一个新的一级缓存实例(本质上是一个HashMap) 被创建。当SqlSession关闭 (close()) 时,一级缓存被销毁。 - 工作原理: 当同一个
SqlSession执行完全相同的查询(相同的MappedStatementID、相同的参数、相同的分页信息等)时,第一次查询会从数据库获取数据并将其放入一级缓存中。后续的相同查询会直接从一级缓存中获取结果,而不会再次访问数据库。 - 缓存数据: 缓存的是查询结果的对象引用本身。
- 失效场景:
- 当
SqlSession关闭时。 - 当执行了
commit()操作(即使没有实际修改数据),MyBatis 会认为数据可能已变更。 - 当执行了任何增删改 (INSERT, UPDATE, DELETE) 操作时,无论这些操作是否影响到缓存中的数据,MyBatis 都会清空当前
SqlSession的一级缓存,以防止读到脏数据。 - 手动调用
sqlSession.clearCache()方法。
- 当
- 特点:
- SqlSession 隔离: 每个
SqlSession拥有自己独立的一级缓存,不同SqlSession之间的一级缓存互不影响。 - 默认开启: 无需额外配置。
- 生命周期短: 与
SqlSession绑定。
- SqlSession 隔离: 每个
2. 二级缓存 (Global Cache / Namespace Cache)
- 定义: 二级缓存是 Mapper Namespace 级别(或称为
SqlSessionFactory级别)的缓存。它可以被多个SqlSession共享。 - 作用域: 它的生命周期与应用程序(或者说
SqlSessionFactory)相同,只要应用程序运行,二级缓存就可能存在(除非被配置的策略清除)。 - 工作原理: 当一个
SqlSession执行查询并提交 (commit()) 后,如果该查询配置了使用二级缓存,查询结果会被存入对应的 Mapper Namespace 的二级缓存中。当其他SqlSession执行相同的查询时,会先尝试从该 Namespace 的二级缓存中获取数据。 - 缓存数据: 为了能在多个
SqlSession之间安全共享,并且支持序列化到磁盘或分布式缓存,二级缓存通常存储的是查询结果对象的序列化副本(除非配置为readOnly=true,这时存储的是对象引用,存在线程安全风险)。因此,放入二级缓存的对象必须实现Serializable接口。 - 开启配置: 二级缓存默认是关闭的。需要手动配置才能开启:
- 在
mybatis-config.xml全局配置文件中开启二级缓存总开关:<settings> <setting name="cacheEnabled" value="true"/> </settings> - 在需要开启二级缓存的 Mapper XML 文件中使用
<cache/>标签:<mapper namespace="com.example.mapper.UserMapper"> <!-- 开启二级缓存,可配置 eviction, flushInterval, size, readOnly 等属性 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="false"/> <select id="selectUserById" resultType="User" useCache="true"> <!-- useCache 默认为 true --> SELECT * FROM users WHERE id = #{id} </select> <update id="updateUser" flushCache="true"> <!-- flushCache 默认为 true --> UPDATE users SET name = #{name} WHERE id = #{id} </update> </mapper> - 确保相关实体类实现了
java.io.Serializable接口。
- 在
- 失效场景:
- 当同一个 Mapper Namespace 下执行了任何增删改 (INSERT, UPDATE, DELETE) 操作(并且对应的语句没有设置
flushCache="false")时,该 Namespace 的二级缓存会被清空(或根据配置可能只清除部分)。这是为了保证数据的一致性。 - 可以通过
<cache/>标签的flushInterval属性设置定时刷新。 - 可以通过
<cache/>标签的eviction属性设置缓存淘汰策略(如 LRU, FIFO)。 - 可以手动通过代码获取 Cache 对象并调用
clear()方法。
- 当同一个 Mapper Namespace 下执行了任何增删改 (INSERT, UPDATE, DELETE) 操作(并且对应的语句没有设置
- 特点:
- 跨 SqlSession 共享: 多个
SqlSession可以共享同一个 Namespace 的缓存数据。 - 默认关闭: 需要显式配置开启。
- 生命周期长: 与应用程序生命周期相关。
- 配置灵活: 可以配置淘汰策略、刷新间隔、缓存大小、只读/读写模式、集成第三方缓存(如 Ehcache, Redis)等。
- 数据一致性风险: 由于是共享缓存,如果数据被外部系统修改,或者在分布式环境下节点间缓存同步不及时,可能导致读到脏数据。
readOnly="false"(默认)通过返回序列化副本降低了对象被意外修改的风险,但有性能开销;readOnly="true"性能更好但有线程安全和数据修改风险。
- 跨 SqlSession 共享: 多个
一级缓存和二级缓存的主要区别总结:
| 特性 | 一级缓存 (Level 1 Cache) | 二级缓存 (Level 2 Cache) |
|---|---|---|
| 作用域 | SqlSession 级别 | Mapper Namespace 级别 (跨 SqlSession) |
| 生命周期 | 与 SqlSession 同步,Session 关闭即销毁 | 与应用程序生命周期相关,直到被清除或应用停止 |
| 共享性 | 不共享,每个 SqlSession 独立 | 共享,同一 Namespace 下的多个 SqlSession 可共享 |
| 默认状态 | 默认开启 | 默认关闭 |
| 存储内容 | 对象引用 | 对象序列化副本 (通常,除非 readOnly=true) |
| 配置 | 无需配置 (基本无法关闭) | 需要全局 <setting> 和 Mapper <cache/> 标签配置,实体需序列化 |
| 主要目的 | 减少同一事务/会话内的重复数据库查询 | 减少不同事务/会话间对相同数据的重复数据库查询 |
| 数据一致性 | 相对容易保证(事务内) | 需谨慎处理,尤其在并发或分布式场景下可能存在脏读风险 |
缓存查询顺序:
当执行一个查询时,MyBatis 会按照以下顺序查找缓存:
- 查找二级缓存 (如果配置开启)。
- 如果二级缓存未命中,查找一级缓存。
- 如果一级缓存也未命中,查询数据库。
- 数据库查询结果会放入一级缓存。
- 如果配置了二级缓存,当
SqlSession提交 (commit()) 或关闭 (close()) 时,一级缓存中的特定数据会刷新到二级缓存中。
317

被折叠的 条评论
为什么被折叠?



