Spring Cache 如何缓存空结果,避免反复查数据库?
Spring Cache 如何缓存空结果避免反复查数据库开篇用户不存在查询结果是 null。若每次都把它当成“缓存没命中”相同请求还是会反复查数据库。要缓存空结果先得分清没查过还是查过但没有。环境JDK 21、Spring Framework 6、Spring Boot 3。示例使用应用自有的 RedisCache 适配器不是 Spring Data Redis 的同名类本地往返验证使用隔离 Redis 3不代表生产版本选型。坑点坑点一只看结果是不是 null丢掉了命中状态Spring Cache 的返回需要分清命中状态cache.get(key) 的结果含义返回 null没命中返回 ValueWrapper里面是 null命中了空结果返回 ValueWrapper里面有值命中了普通结果如果拿到包装器后只判断里面的值就会把第二种情况重新送去查库。正确写法避坑一先看有没有包装器再决定是否加载适配器的加载入口保留了这层区别。命中时直接返回里面的值里面是 null 也不会再执行加载器SuppressWarnings(unchecked)OverridepublicsynchronizedTTget(Objectkey,CallableTvalueLoader){paramNotNull(key,key);ValueWrapperresultthis.get(key);if(result!null){return(T)result.get();}else{TvaluevalueFromLoader(key,valueLoader);this.put(key,value);returnvalue;}}这里的 synchronized 锁住当前缓存对象的这个方法不是分布式锁也不是按 key 加锁。本篇只验证顺序调用时空结果不重复加载不把它当成跨实例并发查询的解决方案。空结果实际存了什么Redis 里不能依靠“没有值”表示“已缓存 null”。写入前继承自 AbstractValueAdaptingCache 的 toStoreValue 会在允许空值时用 NullValue 占位禁止空值时写 null 会抛异常。Overridepublicvoidput(Objectkey,Objectvalue){paramNotNull(key,key);longexpireTimeMillisthis.expireTimeMillis;if(expireTimeMillis0L){this.redisClient.vSet(key.toString(),this.toStoreValue(value));}else{this.redisClient.vSet(key.toString(),this.toStoreValue(value),expireTimeMillis,TimeUnit.MILLISECONDS);}}读取端把占位对象还原成业务上的 nullOverridepublicObjectfromStoreValue(ObjectstoreValue){returnstoreValuenull||storeValueinstanceofNullValue?null:storeValue;}包装器的命中判断发生在这层值还原之外。因此占位对象经过序列化、Redis存储、反序列化后仍须能被识别成 NullValue不能意外变成普通 Map。换序列化器后需要验证这条往返链路。调用示例连续查询两次只加载一次RedisClient 是应用封装需先接好 RedisTemplate、序列化器和 key 配置它不是 JDK 类。下面假定 redisClient 已初始化key 为隔离测试键。AtomicInteger 只用来统计加载次数没有访问真实用户表。RedisCachecachenewRedisCache(users,true,60_000,redisClient);Stringkeyuser:missing;cache.evict(key);AtomicIntegerloadsnewAtomicInteger();CallableObjectloader()-{loads.incrementAndGet();returnnull;};System.out.println(cache.get(key,loader));// nullSystem.out.println(cache.get(key,loader));// nullSystem.out.println(loads.get());// 1Cache.ValueWrapperhitcache.get(key);System.out.println(hit!null);// trueSystem.out.println(hit.get());// nullcache.evict(key);System.out.println(cache.get(key)null);// true参数 true 开启空值缓存60_000 是本适配器配置的过期毫秒数。RedisClient 还可能按配置追加随机过期时间上述验证关闭了这个随机量。有效期内新增用户时需要删除对应空结果否则会暂时继续返回“用户不存在”。正常接入 Spring 缓存注解还需要注册 CacheManager、命名缓存并开启缓存支持。仅有这个类不会让 Cacheable 自动生效如果注解用 unless 排除了 null空结果也不会进入缓存。删除与有效期要一并确认这份适配器的单键删除会调用 RedisClient但 clear 没有清理逻辑Overridepublicvoidevict(Objectkey){paramNotNull(key,key);this.redisClient.kDelete(key.toString());}Overridepublicvoidclear(){}因此不能把 CacheEvict(allEntries true) 当作已经实现的批量清除方案。此处的 key 直接传给客户端不会自动加上 cacheName不同业务应使用不会冲突的 key 命名。总结缓存空结果关键是保留“是否命中”和“结果是什么”这两层信息。开启空值存储、验证占位对象往返再配合有效期和单键失效才能减少同一空结果的重复查询随机 key、并发首次加载和批量清理仍需分别处理。如果这篇文章对您有用欢迎关注。后续会继续拆解 Java 后端的实用代码讲清写法、原理和使用时要注意的细节。

相关新闻

业务认知资产:指标口径、业务逻辑与分析经验的三位一体管理

业务认知资产:指标口径、业务逻辑与分析经验的三位一体管理

1. 这不是在建数据库,而是在沉淀“业务认知资产”你有没有遇到过这样的场景:财务部说的“客户留存率”和运营部汇报的“次月留存率”,分母口径完全不同,但两个数字都出现在CEO周报里;数据看板上“GMV”指标突然跳变30%…

2026/9/16 7:55:12 阅读更多 →
多维数据分析:边际频数与边际比例的应用解析

多维数据分析:边际频数与边际比例的应用解析

1. 多维数据表的描述性分析工具解析在数据分析工作中,我们经常需要处理包含多个变量的复杂数据集。面对这样的多维数据表,如何快速把握各个变量的分布特征?这就不得不提到两个基础但极其重要的描述性统计工具——边际频数和边际比例。我第一次…

2026/9/16 7:54:12 阅读更多 →
agent-skills:面向AI智能体的可复用能力工程范式

agent-skills:面向AI智能体的可复用能力工程范式

1. “agent-skills”不是项目名,而是工程能力的具象化表达“agent-skills”这个词在当前技术社区里频繁出现,但它既不是某个开源库的官方名称,也不是 npm 上可直接 install 的包——它本质上是一组面向智能体(Agent)开…

2026/9/16 7:54:12 阅读更多 →

最新新闻

PLC培训班学完为何还是不会干活?真实项目差距与补课路径

PLC培训班学完为何还是不会干活?真实项目差距与补课路径

1. 先聊一个扎心的事实:培训班教的是“编程”,但工作要的是“干活”我见过太多这样的场景了。花了大几千报了PLC培训班,理论课上得满满当当,笔记本上记了几十页指令表和梯形图案例,结业考试也过了,拿着证书…

2026/9/16 8:46:20 阅读更多 →
阿里成立ATH事业群,大模型战略集结!普通人抓住AI高薪风口,转行窗口期已开启,速收藏!

阿里成立ATH事业群,大模型战略集结!普通人抓住AI高薪风口,转行窗口期已开启,速收藏!

阿里成立ATH事业群,由CEO亲自带队,核心目标为创造、输送和应用Token,标志着AI战略落地进入深水区。脉脉数据显示,AI岗位平均月薪60738元,比新经济行业高26.04%,人才供不应求。AI岗位需求暴涨34.39%&#xf…

2026/9/16 8:46:20 阅读更多 →
代码注入与Hook技术原理及Android实践

代码注入与Hook技术原理及Android实践

1. 代码注入技术解析代码注入是将外部代码植入目标进程并执行的技术手段,在安全研究、逆向工程、性能监控等领域有广泛应用。理解代码注入需要从操作系统层面把握三个关键要素:进程控制权转移、内存空间隔离突破和代码执行环境构建。1.1 进程控制权交接原…

2026/9/16 8:46:20 阅读更多 →
ADS8689单电源双极性采样实战:参考电压与信号调理全解析

ADS8689单电源双极性采样实战:参考电压与信号调理全解析

简介:本资源是一套基于STM32F103单片机驱动ADS8689 24位高精度ADC的嵌入式开发实践代码,面向嵌入式软硬件工程师、电子类专业学生及工业测量系统开发者,解决双极性模拟信号在单电源供电条件下的高分辨率采集难题。压缩包仅含2个核心文件&…

2026/9/16 8:46:20 阅读更多 →
ESP32通过IIC读取QMA6100P加速度计:寄存器配置与Arduino驱动

ESP32通过IIC读取QMA6100P加速度计:寄存器配置与Arduino驱动

简介:面向物联网嵌入式开发者的ESP32 Arduino例程包,以IIC协议采集QMA6100P三轴加速度值为核心,演示从传感器驱动到串口输出的完整流程,适合正在学习ESP32或需要快速移植加速度传感器的开发者参考。压缩包共15个文件,其…

2026/9/16 8:46:20 阅读更多 →
Colibri:专为MoE架构优化的纯C高性能推理引擎

Colibri:专为MoE架构优化的纯C高性能推理引擎

1. 项目概述:Colibri 是什么,它解决的不是“跑得快”,而是“算得巧”Colibri 这个名字乍一听像某种蜂鸟——轻盈、敏捷、能量密度极高。这恰恰是它在当前大模型推理领域最精准的隐喻。它不是一个通用大模型,也不是一个训练框架&am…

2026/9/16 8:45:19 阅读更多 →

日新闻

嵌入式三大高薪赛道:车规功能安全、RISC-V固件架构、边缘AI部署

嵌入式三大高薪赛道:车规功能安全、RISC-V固件架构、边缘AI部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/16 0:00:51 阅读更多 →
IoT-For-Beginners 智能语音计时器:Wio Terminal 基于 DMAC 与 Flash 的音频采集实战

IoT-For-Beginners 智能语音计时器:Wio Terminal 基于 DMAC 与 Flash 的音频采集实战

IoT-For-Beginners 智能语音计时器:Wio Terminal 基于 DMAC 与 Flash 的音频采集实战 【免费下载链接】IoT-For-Beginners 12 Weeks, 24 Lessons, IoT for All! 项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners 本指南聚焦 GitHub Tren…

2026/9/16 0:01:52 阅读更多 →
基于MATLAB的CRI显色指数计算:从SPD光谱到Ra的完整流程

基于MATLAB的CRI显色指数计算:从SPD光谱到Ra的完整流程

简介:针对照明设计与光学研究中的光谱功率分布(SPD)与显色性指数(CRI)计算需求,这套MATLAB程序为照明工程师、LED研发人员及光学专业学生提供了轻量工具。代码通过解析光谱测量数据,自动完成波长…

2026/9/16 0:01:52 阅读更多 →

周新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/15 12:27:42 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/16 1:59:46 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/16 1:59:35 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/15 21:40:00 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/15 21:39:18 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/15 21:40:17 阅读更多 →