直接用RedisTemplate裸调易出错,因序列化器不统一、泛型擦除致反序列化失败、空值/TTL/单位需手动处理;优雅封装需统一序列化(key/String、value/GenericJackson)、自动判空、语义化命名、显式传Class类型参数。

直接用 RedisTemplate 写业务代码,很快就会被 opsForValue()、opsForHash() 套娃搞晕,更别说序列化错乱、空值不处理、TTL单位写反这些低级但高频的问题。真正“优雅”的封装,核心不是加多少方法,而是让每个操作都默认走对的序列化、自动判空、统一异常、带语义命名。
为什么不能直接用 opsForXxx() 链式调用?
因为每次都要显式获取 Operations 对象,且返回类型是泛型擦除后的 Object,强转易出错;更重要的是,opsForValue().get("key") 返回 null 时,你得自己判断是不是缓存穿透,而 opsForHash().entries("hkey") 返回的是 Map,但 key/value 的序列化可能不一致——这些细节在裸用 RedisTemplate 时全靠人肉盯。
-
opsForValue()和opsForHash()使用的序列化器可能不同(比如 key 用StringRedisSerializer,value 却用了Jackson2JsonRedisSerializer),导致 hash 中的 field 查不到 - 调用
boundValueOps("k").get()后,再调expire()必须手动传 TimeUnit,一不留神写成TimeUnit.SECONDS而实际想设 30 分钟,就变成 30 秒 - 没有默认的空值策略:
get("missing")返回null,但业务上往往需要返回 Optional 或抛自定义异常
Key 和 Value 序列化必须分开配,且 Hash/HashValue 不能漏
只配 setKeySerializer() 和 setValueSerializer() 是不够的。opsForHash() 内部会分别使用 hashKeySerializer 和 hashValueSerializer,如果没显式设置,它会 fallback 到默认的 JdkSerializationRedisSerializer,这就和 string 操作不一致了。
- key 类型统一用
StringRedisSerializer(保证 redis-cli 里能直接看到可读 key) - value / hashValue 统一用
GenericJackson2JsonRedisSerializer(注意不是Jackson2JsonRedisSerializer,前者支持泛型反序列化) - hashKey 同样用
StringRedisSerializer,避免 map 的 field 出现乱码 - 配置里漏掉
setHashKeySerializer()或setHashValueSerializer(),会导致hgetall返回空 Map 或 ClassCastException
工具类方法命名要带业务语义,别堆 API
不要暴露 redisUtil.hSet("user:1", "name", "zhang") 这种接口。它没说明 value 是否序列化、field 是否字符串、是否覆盖。应该按场景切分:
-
hSetIfAbsent(String hashKey, String field, Object value):仅当 field 不存在时写入,避免并发覆盖 -
hMGetAsMap(String hashKey, String... fields):返回Map,自动反序列化每个 value,空 field 自动跳过 -
sAddWithExpire(String setKey, long expireSeconds, Object... values):set 操作 + TTL 一步到位,单位固定为秒,不用选 TimeUnit -
zRangeWithScores(String zKey, long start, long end):返回List,但自动把 score 转成 double,tuple.getValue() 直接是反序列化后的对象>
泛型擦除导致反序列化失败,必须传 Class
GenericJackson2JsonRedisSerializer 在反序列化时需要知道目标类型,但 redisTemplate.opsForValue().get("k") 返回的是 Object,泛型信息已丢失。工具类里所有 get 方法,如果要返回具体类型,必须显式接收 Class 参数。
- 错误写法:
public—— 强转必然失败T get(String key) { return (T) opsForValue().get(key); } - 正确写法:
public,前提是 template 已配好T get(String key, Class clazz) { return (T) jsonRedisTemplate.opsForValue().get(key); } GenericJackson2JsonRedisSerializer - 更稳妥的是用
ObjectMapper.readValue(jsonStr, clazz)手动反序列化,绕过 template 的泛型陷阱
最易被忽略的一点:Lettuce 客户端在 Spring Boot 2.3+ 默认启用 SSL 和响应超时,如果你的 Redis 服务没开 SSL,又没关掉 lettuce:use-ssl: false,连接会静默失败,日志里只报 Connection refused,根本看不出是协议问题。











