在spring data redis中处理缓存空值,核心做法是利用`@cacheable`注解的`unless`属性或自定义`keygenerator`结合`optional`包装返回值,从而避免将`null`或空对象写入redis。具体而言,当业务逻辑返回`optional.empty()`时,通过spel表达式判断结果是否为空,若为空则跳过缓存写入操作,确保redis中只存储有效数据。

如何配置@Cacheable以拦截空值
直接回答:通过在`@Cacheable`注解中使用`unless`属性,可以基于方法返回值动态决定是否缓存。例如,使用`#result.isEmpty()`作为条件,当`Optional`为空时,Spring Cache不会执行实际的缓存写入动作,从而保持Redis状态干净。

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
@Cacheable(value = "users", key = "#id", unless = "#result.isEmpty()")
public Optional<user> getUserById(Long id) {
// 模拟数据库查询,可能返回空
User user = database.find(id);
return Optional.ofNullable(user);
}
}</user>
上述代码中,`value`指定了Redis中的键空间前缀,`key`定义了缓存的唯一标识。关键在于`unless = "#result.isEmpty()"`,这行SpEL表达式会在方法执行后检查返回值。如果`getUserById`返回`Optional.empty()`,Spring Cache框架会拦截后续的`put`操作,直接返回空值而不触碰Redis。这种方法简单且无需修改底层缓存管理器配置,适用于大多数基于Spring Boot的项目。
如何处理缓存穿透导致的重复查询
直接回答:虽然`unless`能防止空值入缓存,但对于频繁请求不存在的ID,仍会导致每次请求都穿透到数据库。解决此问题的标准做法是使用布隆过滤器或在数据库中为不存在的记录设置一个特殊的“占位符”对象并缓存它,但更推荐的做法是在Service层增加显式的空值检查逻辑,或者使用Caffeine等本地缓存作为一级缓冲,配合Redis二级缓存形成多层防护。

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class EnhancedUserService {
// 一级缓存:本地Caffeine,快速拦截已知不存在的ID
private final com.github.benmanes.caffeine.cache.Cache<long boolean> localCache =
com.github.benmanes.caffeine.cache.Caffeine.newBuilder().maximumSize(1000).build();
@Cacheable(value = "users", key = "#id", unless = "#result.isEmpty()")
public Optional<user> getUserWithBloomFilter(Long id) {
// 检查本地缓存是否标记为不存在
if (localCache.getIfPresent(id) != null) {
return Optional.empty();
}
User user = database.find(id); // 假设这是DB调用
if (user == null) {
// 标记该ID不存在,防止缓存穿透
localCache.put(id, true);
return Optional.empty();
}
return Optional.of(user);
}
}</user></long>
在这个增强版示例中,我们引入了本地缓存来应对高频的空值查询。当`database.find(id)`返回`null`时,我们将该ID放入`localCache`并立即返回`Optional.empty()`。由于`localCache`的存在,后续相同的ID请求不会到达Redis甚至数据库层。同时,`@Cacheable`依然保留用于缓存真实存在的用户数据。这种组合策略既利用了Redis的分布式特性,又通过本地缓存减轻了后端压力,是处理高并发下空值缓存问题的常见最佳实践。
当Redis连接异常时Optional的行为表现
直接回答:当Redis连接失败或超时,而`@Cacheable`未配置`cacheManager`的回退机制时,Spring Cache通常会抛出`CacheException`或`RedisConnectionException`,导致整个请求中断。此时`Optional`无法被正常返回,因为缓存读取阶段就已经失败。为了避免这种情况,应配置`CacheErrorHandler`或使用`@CacheConfig`中的`sync=true`配合合理的超时设置,确保在缓存不可用时能降级到直接查询数据库并返回`Optional`结果。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











