缓存键设计须带业务前缀和参数标识,避免冲突与难维护;condition和unless需配合业务逻辑控制缓存边界;缓存失效应主动触发而非依赖过期;序列化器必须统一配置为JSON以保证可读性与跨语言兼容。

缓存键设计必须带业务前缀和参数标识
直接用 #id 作 key 看似简洁,但实际会撞库、难排查、无法清理。比如两个不同实体都用 @Cacheable(key = "#id"),缓存值互相覆盖,查 A 表却返回 B 表数据。
正确做法是显式构造带上下文的 key:
@Cacheable(value = "user", key = "'user:id:' + #id")@Cacheable(value = "order", key = "'order:sn:' + #sn")- 批量查询场景避免用
#root.args全参拼接(易哈希冲突),改用Arrays.asList(#ids).toString()或自定义 keyGenerator
Redis 中 key 命名混乱会导致运维困难——你没法用 keys user:* 快速定位或清空某类缓存。
@Cacheable 的 condition 和 unless 必须配合业务逻辑使用
缓存不是“全量兜底”,而是有策略地命中。不加条件直接缓存所有结果,可能把错误数据、空对象、敏感信息也塞进 Redis。
典型误用:@Cacheable(value = "user", key = "#id") 对 null 返回值也缓存,下次查不到就永远返回 null。
应明确控制缓存边界:
-
condition = "#id != null"—— 参数非法时不走缓存代理 -
unless = "#result == null"—— 方法返回 null 就不写入缓存 -
unless = "#result.status == 'DELETED'"—— 逻辑删除状态不缓存
注意:unless 是在方法执行后判断,condition 是在执行前判断,二者语义不能互换。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
缓存失效必须主动触发,不能只靠过期时间
仅设 timeToLive = 300(5 分钟)看似简单,但数据变更后缓存仍脏读长达 5 分钟,用户看到旧数据、管理员修改不生效,这类问题在线上极难复现。
真实项目中必须搭配主动失效:
- 新增/修改/删除操作后,立刻调用
cacheManager.getCache("user").evict(key)或@CacheEvict(value = "user", key = "'user:id:' + #id") - 批量更新时慎用
@CacheEvict(allEntries = true),它会清空整个 cache,引发雪崩;改用@CacheEvict(value = "user", key = "#user.id")逐条清理 - 如果用 RedisTemplate 手动操作,记得 key 格式要和
@Cacheable完全一致,否则失效失败
缓存一致性不是“等它过期”,而是“改完立刻通知失效”——这是多数人忽略的临界点。
序列化器不配对会导致 Redis 存乱码、取不出对象
Spring Boot 2.0+ 默认用 JdkSerializationRedisSerializer,存进去是二进制字节流,Redis CLI 里看就是一串不可读字符,且其他语言客户端根本没法解析。
更严重的是:如果你在代码里用 RedisTemplate<string user></string>,但没指定 value 序列化器,opsForValue().get("user:id:1") 可能返回 null 或抛 SerializationException。
必须统一配置 JSON 序列化:
- 声明
RedisTemplateBean 时,设置setKeySerializer(new StringRedisSerializer())和setValueSerializer(new GenericJackson2JsonRedisSerializer()) - 或直接用
StringRedisTemplate处理字符串,对象转 JSON 后存:stringRedisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(obj))
序列化不一致的问题往往只在跨服务、跨语言或人工查 Redis 时暴露,等线上出问题再调,已经晚了。


















