ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Cache 如何缓存空结果,避免反复查数据库?

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 后端的实用代码讲清写法、原理和使用时要注意的细节。
返回列表