MyBatis 一级缓存、二级缓存与清理机制
MyBatis 缓存面试常见问法是“一级缓存和二级缓存用过吗”不要只背“一级缓存是 SqlSession 级别二级缓存是 namespace 级别”。更好的回答是讲清楚缓存作用域、什么时候命中、什么时候清空、为什么二级缓存要谨慎使用。命中未命中是命中未命中否Mapper 查询先查一级缓存直接返回结果是否开启二级缓存查询 namespace二级缓存查询数据库写入一级缓存SqlSession 关闭或提交后写入二级缓存一级缓存是什么一级缓存是 MyBatis 默认开启的本地缓存作用域是SqlSession。它底层基于PerpetualCache本质上可以理解成一个HashMap。同一个SqlSession中执行完全相同的查询第一次查数据库第二次可能直接命中缓存。SqlSessionsqlSessionsqlSessionFactory.openSession();UserMappermapper1sqlSession.getMapper(UserMapper.class);UserMappermapper2sqlSession.getMapper(UserMapper.class);Useruser1mapper1.selectById(6);Useruser2mapper2.selectById(6);sqlSession.close();如果中间没有刷新缓存两个查询在同一个SqlSession内通常只会执行一次 SQL。一级缓存会在这些情况下清空SqlSession执行insert、update、delete。手动执行clearCache()。SqlSession关闭。查询配置要求刷新缓存。二级缓存是什么二级缓存的作用域是 namespace也就是 Mapper 级别。它不依赖单个SqlSession不同SqlSession之间可以共享。SqlSessionsqlSession1sqlSessionFactory.openSession();UserMappermapper1sqlSession1.getMapper(UserMapper.class);Useruser1mapper1.selectById(6);sqlSession1.close();SqlSessionsqlSession2sqlSessionFactory.openSession();UserMappermapper2sqlSession2.getMapper(UserMapper.class);Useruser2mapper2.selectById(6);sqlSession2.close();如果二级缓存开启并且第一次会话关闭后数据进入二级缓存第二个会话查询相同数据时就可能命中缓存。二级缓存默认不开启需要两步settingssettingnamecacheEnabledvaluetrue//settings在 Mapper 映射文件中开启mappernamespacecom.example.UserMappercache//mapper同时二级缓存中的对象通常需要实现Serializable接口。一级缓存和二级缓存对比维度一级缓存二级缓存作用域SqlSessionnamespace/ Mapper是否默认开启默认开启默认不开启是否跨会话不跨会话可以跨会话底层实现PerpetualCache默认也是PerpetualCache清理时机session flush、更新、关闭当前 namespace 发生增删改后清理使用建议默认即可谨慎使用关注数据一致性二级缓存什么时候清理缓存最大的问题不是能不能命中而是数据什么时候失效。否是执行 select 查询结果进入缓存后续相同查询命中缓存同作用域是否发生写操作继续复用缓存insertupdatedelete清空该作用域缓存下一次查询重新访问数据库MyBatis 默认规则是某个作用域发生新增、修改、删除操作后该作用域下相关 select 缓存会被清空。比如UserMappernamespace 下执行了updateUser那么UserMapper这个 namespace 下的二级缓存会被清理避免后续读到旧数据。但这也带来一个问题如果多个 Mapper 操作的是同一张表而二级缓存按 namespace 隔离缓存一致性就容易变复杂。为什么二级缓存要谨慎二级缓存看起来很美好但实际项目中要谨慎数据更新频繁时缓存很容易被频繁清空收益不大。多个 namespace 操作同一批数据时缓存一致性不好控制。分布式部署下本地二级缓存无法天然跨节点一致。业务已经使用 Redis 这类集中式缓存时MyBatis 二级缓存容易和业务缓存叠加出复杂问题。所以真实项目里MyBatis 一级缓存默认使用即可跨请求、跨服务的热点数据缓存通常会交给 Redis 这类专门缓存组件。面试回答模板可以这样回答MyBatis 一级缓存是SqlSession级别的本地缓存默认开启底层基于PerpetualCache可以理解成 HashMap。同一个SqlSession中相同查询可能命中一级缓存当 session 发生增删改、flush、clear 或 close 时缓存会清空。二级缓存是 namespace 或 Mapper 级别的缓存默认关闭需要全局开启cacheEnabled并在 Mapper 中配置cache/。二级缓存可以跨SqlSession但只有会话提交或关闭后一级缓存中的数据才会进入二级缓存。如果继续问清理机制可以补当某个作用域发生新增、修改、删除操作后MyBatis 默认会清空该作用域下 select 缓存避免读到旧数据。但二级缓存按 namespace 隔离多 Mapper、多服务场景下数据一致性比较难控制所以实际项目中要谨慎使用。小结MyBatis 缓存要抓住两个词作用域和失效。一级缓存解决的是同一个SqlSession内重复查询二级缓存解决的是同一个 Mapper namespace 下跨会话复用结果。缓存能不能用不只看能不能命中更要看数据更新后能不能及时失效。