Spring Ehcache仅同请求内生效,页面刷新重复查询DB求助
缓存失效问题排查与解决方案
我来帮你梳理下这个缓存不命中的问题,结合你的配置和测试情况,咱们一步步拆解分析:
核心问题复盘
- 同一次请求内重复调用服务方法能命中缓存,但页面刷新(新请求)时首次调用必查DB
- 单元测试原本跨用例能命中缓存,但给对象实现
hashCode()和equals()后,反而每次都触发DB查询
关键排查方向
1. 缓存Key的生成逻辑是核心痛点
Spring缓存的命中完全依赖Key的一致性,而你用{#options, #pageable}作为Key,这完全取决于这两个对象的equals()和hashCode()实现是否正确。
排查点:
- 对象属性是否全量参与判断:确保
PhoneOption和MyPage的equals()/hashCode()只包含与查询条件相关的核心属性:- 比如
PhoneOption只需要比较name、status这些查询用的字段,无关属性别加进去 MyPage只需要比较page、size这两个分页参数,不要包含totalRows(这个是方法返回后赋值的,入参里并没有)
- 比如
- null安全的比较逻辑:一定要用
Objects.equals()来比较属性,避免null值导致的判断错误。比如:// PhoneOption的正确equals示例 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PhoneOption that = (PhoneOption) o; return Objects.equals(status, that.status) && Objects.equals(name, that.name); } @Override public int hashCode() { return Objects.hash(status, name); } - 临时打印Key验证:在服务方法开头加日志,对比跨请求、单元测试中的Key哈希值是否一致:
System.out.println("Combined Key Hash: " + Arrays.asList(options, pageable).hashCode());
2. 简化缓存Key,绕开对象相等性问题
如果怀疑对象的equals()/hashCode()有问题,可以先把Key改成具体的属性组合,直接用参数值生成Key,这样更可靠:
@Cacheable(value = "myPageCache", key = "{#options.name, #options.status, #pageable.page, #pageable.size}") public MyPage<PhoneInfo>findAllByOption(PhoneOption options, MyPage<PhoneInfo> pageable){ // ... 原有逻辑 }
这种方式不需要依赖对象的相等性判断,直接用查询的核心参数生成Key,能快速验证缓存是否能正常跨请求命中。
3. 单元测试的反向问题分析
你提到实现equals()/hashCode()后,单元测试反而每次都查DB,这说明:
- 之前未实现这两个方法时,Spring用的是对象的内存地址生成Key,但你的单元测试可能因为某些原因(比如对象复用、测试框架的特殊处理)导致Key一致
- 实现方法后,反而因为属性判断逻辑有误,导致每次生成的Key不同
建议检查:
MyPage的equals()是否误判了totalRows或其他非入参属性PhoneOption的属性是否有默认值不一致的情况(比如包装类null和0的区别)
4. 缓存配置与生命周期验证
你的Ehcache配置看起来没问题,但可以做几个小验证:
- 检查
timeToLiveSeconds和timeToIdleSeconds设置为86400,缓存不会快速过期 - 开启Spring缓存调试日志,看缓存命中的详细过程:
在日志配置中添加:
日志会显示每次请求的Key、是否命中缓存、缓存写入的情况,能快速定位问题。<logger name="org.springframework.cache" level="DEBUG"/> <logger name="net.sf.ehcache" level="DEBUG"/>
快速修复步骤
- 先把
findAllByOption的缓存Key改成具体属性组合,验证跨请求是否能命中缓存 - 如果能命中,再回头调试
PhoneOption和MyPage的equals()/hashCode()实现,确保只包含核心查询属性 - 调试单元测试中的对象属性,确认每次传入的参数属性完全一致
内容的提问来源于stack exchange,提问作者Philip YW
相关产品推荐
相关产品推荐

