一级缓存默认开启,作用域为SqlSession。当同一个SqlSession执行相同的SQL查询时,MyBatis会优先从一级缓存中取结果。
SqlSession执行增删改操作(insert、update、delete):
任何增删改操作都会清空当前SqlSession的一级缓存,防止出现脏数据。
SqlSession提交(commit)或回滚(rollback):
执行commit()
或rollback()
方法时会清空一级缓存。
手动清空缓存:
调用SqlSession.clearCache()
方法会清空当前SqlSession的一级缓存。
执行SQL语句时设置了flushCache=true
:
在映射文件中,如果某条语句配置了flushCache="true"
,执行该语句会清空一级缓存(和二级缓存)。
示例:
<select id="selectUser" resultType="User" flushCache="true"> SELECT * FROM user WHERE id = #{id} </select>
查询不同的结果集:
当使用同一个方法查询不同参数时,会缓存不同的结果,但不会导致之前缓存失效。只有上述操作才会主动清除。
二级缓存需要手动开启,作用域为Mapper(Namespace),多个SqlSession共享。
在mybatis配置文件中开启全局缓存(默认开启,通常不需要设置):
<settings> <setting name="cacheEnabled" value="true"/> </settings>
在Mapper映射文件中添加<cache/>
标签。
执行增删改操作:
同一个Mapper中的增删改操作(insert、update、delete)执行后,会清空该Mapper的二级缓存。
注意:配置了flushCache="true"
的查询语句也会清空二级缓存(一般不这样配置查询)。
事务提交(commit):
二级缓存只在SqlSession执行commit()
或close()
时才会生效(将新数据刷入二级缓存),所以未提交的修改不会影响二级缓存。
当多个SqlSession并发操作时,一个SqlSession提交才会更新二级缓存,此时其他SqlSession获取的是更新后的缓存。
手动清空缓存:
通过程序调用org.apache.ibatis.cache.Cache
的clear()
方法可以清空二级缓存(不常用)。
配置参数flushInterval
:
如果配置了<cache flushInterval="60000"/>
,则每60秒清空一次缓存。
配置参数size
:
当缓存的对象数量超过设置的大小(默认为1024),会按照策略(如LRU)移除旧缓存。
其他注意事项:
二级缓存是跨SqlSession的,所以当多个SqlSession并发操作时,可能会读取到脏数据(需要合理使用事务隔离级别)。
二级缓存默认是读写锁:同一时刻只有一个SqlSession能写入,写入时会阻塞所有读取操作,直到写入完成(可配置其他策略)。
一级缓存:
在分布式环境下,避免在同一个SqlSession中执行多个可能产生不一致的查询。
对于实时性要求高的查询,可以手动清空缓存或设置flushCache=true
。
二级缓存:
适合读多写少的场景,避免频繁更新导致缓存频繁失效。
实体类需要实现Serializable
接口(因为二级缓存可能将对象序列化存储)。
多个Mapper可以共享缓存,通过<cache-ref>
标签引用同一缓存空间(一般不推荐,因为维护麻烦)。
<!-- UserMapper.xml --> <mapper namespace="com.example.UserMapper"> <cache/> <!-- 开启二级缓存,使用默认配置 --> </mapper>
<cache eviction="LRU" <!-- 缓存回收策略:默认LRU --> flushInterval="60000" <!-- 60秒刷新一次 --> size="1024" <!-- 最大缓存对象数量 --> readOnly="true" <!-- 只读,返回的实例不能被修改,可以提高性能 --> />
MyBatis缓存的失效主要发生在:
一级缓存:SqlSession执行增删改、提交、回滚、手动清空时。
二级缓存:Mapper执行增删改、事务提交、手动清空、超时或达到上限时。
合理使用缓存可以提升性能,但需注意缓存带来的数据一致性问题。对于频繁修改的数据,建议关闭二级缓存,或根据业务场景设置合适的刷新策略。
在 MyBatis 中,缓存失效主要发生在一级缓存(本地缓存)和二级缓存(全局缓存)两个层面。以下是详细的失效场景:
执行写操作时自动失效
sql复制INSERT, UPDATE, DELETE
任何写操作(即使不修改当前缓存数据)都会清空整个一级缓存。
手动调用 clearCache()
java复制sqlSession.clearCache(); // 立即清空当前 SqlSession 的缓存
事务提交/回滚时
java复制sqlSession.commit(); // 或 rollback() // 提交/回滚后缓存自动清空(确保数据一致性)
执行语句时强制刷新
在 Mapper XML 中设置 flushCache="true"
:
xml复制<select id="selectUser" flushCache="true" resultType="User"> SELECT * FROM user WHERE id = #{id}</select>
SqlSession 关闭时
java复制sqlSession.close(); // 所有缓存被销毁
执行写操作后自动失效
同命名空间(namespace)下的任何 INSERT/UPDATE/DELETE
操作会清空整个 namespace 的二级缓存。
示例:执行 UserMapper.updateUser()
会清空所有 UserMapper
的缓存。
手动调用 clear()
注入 Cache
对象强制清空:
java复制Configuration configuration = sqlSession.getConfiguration();Cache cache = configuration.getCache("com.example.UserMapper"); cache.clear(); // 清空 UserMapper 的二级缓存
配置缓存刷新策略
通过 eviction
策略自动淘汰(如 LRU):
xml复制<cache eviction="LRU" flushInterval="60000" size="512"/>
flushInterval
:定期清空缓存(单位:毫秒)
size
:缓存对象上限(超过时按策略淘汰)
事务提交时生效
二级缓存的修改必须等到事务提交后才会更新(未提交的事务不会影响缓存)。
跨 Mapper 缓存问题
如果两个 Mapper 操作同一张表(如 UserMapper
和 OrderMapper
都操作 user
表),需要在 XML 中声明缓存引用:
xml复制<cache-ref namespace="com.example.UserMapper"/>
缓存穿透风险
频繁执行 flushCache="true"
或反复更新数据会导致缓存失效风暴,降低性能。
对象一致性要求
二级缓存的对象必须是可序列化的:
java复制public class User implements Serializable { ... }
读多写少场景:启用二级缓存提升性能。
写密集场景:禁用二级缓存(在配置中关闭):
xml复制<cache enabled="false"/>
关键查询:对实时性要求高的查询显式禁用缓存:
xml复制<select id="getRealTimeData" useCache="false"> ...</select>
诊断工具:通过日志监控缓存命中率:
xml复制<configuration> <settings> <setting name="cacheEnabled" value="true"/> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings></configuration>
缓存级别 | 失效触发条件 | 影响范围 |
---|---|---|
一级缓存 | UPDATE/INSERT/DELETE | 当前 SqlSession 全部 |
sqlSession.clearCache() | ||
事务提交 (commit/rollback) | ||
flushCache="true" 的查询 | ||
二级缓存 | 同 namespace 的写操作 | 整个 namespace 的缓存 |
调用 cache.clear() | ||
到达 flushInterval 时间阈值 | ||
缓存对象数量超过 size 上限 |
合理使用缓存可提升性能,但需根据业务特点权衡一致性与效率。