Redis存储List<T>的解决方法

本文探讨了在Redis中存储List&lt;Object&gt;而非仅限于List&lt;String&gt;的方法。介绍了两种解决方案:一是将对象转换为字符串形式;二是通过序列化和反序列化对象来实现存储。

原文地址:https://blog.youkuaiyun.com/Kincym/article/details/72676813

因为redis虽然支持存储List类型的数据,但是基础的是只支持List<String>,如果我们想存储List<Object>之类的数据,有两种解决办法:

  1. 把我们的对象转化为字符串形式(toString)
  2. 把我们要存储的数据进行序列化(反序列化)
public interface CacheService { @Nullable <T> T get(String cacheName, @NonNull String key, @NonNull Class<T> clazz); /** @deprecated */ @Nullable @Deprecated <T> T get(String cacheName, @NonNull String key, @NonNull BiFunction<String, String, T> loadHandle, @NonNull Class<T> clazz); Stream<String> getKeysByPattern(String cacheName, @NonNull String pattern); <T> void set(String cacheName, @NonNull String key, @NonNull T value); <T> void set(String cacheName, @NonNull String key, @NonNull T value, long timeToLive, @NonNull TimeUnit timeUnit); boolean deleteKey(String cacheName, @NonNull String key); long deleteKeyByPrefix(String cacheName, @NonNull String prefix) throws UnsupportedOperationException; boolean expireKey(String cacheName, @NonNull String key, long timeToLive, @NonNull TimeUnit timeUnit); <T> boolean replace(String cacheName, @NonNull String key, @NonNull T oldValue, @NonNull T newValue); boolean cas(String cacheName, @NonNull String key, @NonNull Version value); long getRemainTimeToLive(String cacheName, @NonNull String key); IAtomicLong getAtomicLong(String cacheName, @NonNull String key); <T> Map<String, T> getHash(String cacheName, @NonNull String key, @NonNull Class<T> clazz); @Nullable <T> T getHashValue(String cacheName, @NonNull String key, @NonNull String hashKey, @NonNull Class<T> clazz); <T> T putHashValue(String cacheName, @NonNull String key, @NonNull String hashKey, @NonNull T value); @Nullable <T> T deleteHashValue(String cacheName, @NonNull String key, @NonNull String hashKey); /** @deprecated */ @Deprecated <T> Map<String, T> getTimedHash(String cacheName, @NonNull String key, @NonNull Class<T> clazz); /** @deprecated */ @Deprecated @Nullable <T> T getTimedHashValue(String cacheName, @NonNull String key, @NonNull String hashKey, @NonNull Class<T> clazz); /** @deprecated */ @Deprecated <T> T putTimedHashValue(String cacheName, @NonNull String key, @NonNull String hashKey, @NonNull T value); /** @deprecated */ @Deprecated <T> T putTimedHashValue(String cacheName, @NonNull String key, @NonNull String hashKey, @NonNull T value, long timeToLive, @NonNull TimeUnit timeUnit); /** @deprecated */ @Deprecated @Nullable <T> T deleteTimedHashValue(String cacheName, @NonNull String key, @NonNull String hashKey); <T> boolean addSortedSetValue(String cacheName, @NonNull String key, @NonNull T value, double score); <T> int addSortedSetValueReturnSize(String cacheName, @NonNull String key, @NonNull T value, double score); <T> Collection<T> rangeSortedByScore(String cacheName, @NonNull String key, double min, double max, @NonNull Class<T> clazz); <T> boolean removeSortedSetValue(String cacheName, @NonNull String key, @NonNull T value); <T> Collection<T> pollSortedByScore(String cacheName, @NonNull String key, double min, double max, @NonNull Class<T> clazz); <T> Collection<T> pollSortedByScore(String cacheName, @NonNull String key, double min, double max, int count, @NonNull Class<T> clazz); <T> Collection<T> pollFirst(String cacheName, @NonNull String key, int count, @NonNull Class<T> clazz); <T> Collection<T> pollFirst(String cacheName, @NonNull String key, int count, double min, double max, @NonNull Class<T> clazz); @Nullable <T> T peekFirst(String cacheName, @NonNull String key, @NonNull Class<T> clazz); int getSortedSetSize(String cacheName, @NonNull String key); <T> Set<T> getSet(String cacheName, @NonNull String key, @NonNull Class<T> clazz); <T> List<T> getList(String cacheName, @NonNull String key, @NonNull Class<T> clazz); <T> Queue<T> getQueue(String cacheName, @NonNull String key, @NonNull Class<T> clazz); <T> Deque<T> getDeque(String cacheName, @NonNull String key, @NonNull Class<T> clazz); <T> BlockingQueue<T> getBlockingQueue(String cacheName, @NonNull String key, @NonNull Class<T> clazz); <T> BlockingDeque<T> getBlockingDeque(String cacheName, @NonNull String key, @NonNull Class<T> clazz); } 这是项目里面的缓存接口,我实现的部分(get)有点问题,如下,怎么修改: @Slf4j @Service public class WebSocketResultCacheService { public static final String WEBSOCKET_RESULT = "websocket:result"; private static final int FILE_ID_TIME_OUT = 30; private static final TimeUnit FILE_ID_TIME_OUT_UNIT = TimeUnit.MINUTES; @Autowired private CacheService cacheService; @Autowired private ObjectMapper objectMapper; // Jackson ObjectMapper /** * @param vmsId vmsId * @param taskId taskId * @return */ public <T> getResult(String vmsId, String taskId) { String cacheKey = buildCacheKey(vmsId, taskId); return cacheService.get(WEBSOCKET_RESULT, cacheKey, ); } /** * @param vmsId vmsId * @param taskId taskId * @param dto WebSocketResultDTO */ public <T> void setValue(String vmsId, String taskId, WebSocketResultDTO<T> dto) { String cacheKey = buildCacheKey(vmsId, taskId); // todo 修改缓存名字 RedisKey.WEBSOCKET_RESULT cacheService.set(WEBSOCKET_RESULT, cacheKey, dto, FILE_ID_TIME_OUT, FILE_ID_TIME_OUT_UNIT); } /** * @param vmsId vmsId * @param taskId taskId */ public void delete(String vmsId, String taskId) { String cacheKey = buildCacheKey(vmsId, taskId); // todo 修改缓存名字 RedisKey.WEBSOCKET_RESULT cacheService.deleteKey(WEBSOCKET_RESULT, cacheKey); } private String buildCacheKey(String vmsId, String taskId) { return String.format("websocket:result:%s:%s", vmsId, taskId); } }
10-10
<think>我们面临的核心问题是:在`WebSocketResultCacheService`的`getResult`方法中调用`CacheService.get`时,需要传递一个`Class<T>`参数,但当前代码中这个参数缺失(在`cacheService.get(WEBSOCKET_RESULT, cacheKey, )`中第三个参数为空)。由于`WebSocketResultDTO<T>`是一个泛型类型,我们不能直接使用`WebSocketResultDTO.class`(因为存在类型擦除),所以需要调整设计。 ### 问题分析 1. `CacheService.get`方法的签名是:`<T> T get(String cacheName, String key, Class<T> clazz)` 2. 我们想获取的是`WebSocketResultDTO<T>`类型,但`T`是运行时不可知的(类型擦除) 3. 当前`getResult`方法也使用了泛型`<T>`,但无法在运行时传递`T`的实际类型 ### 解决方案 #### 方案A:修改`getResult`方法签名,要求传入`Class<T>`类型 ```java public <T> WebSocketResultDTO<T> getResult(String vmsId, String taskId, Class<T> elementType) { String cacheKey = buildCacheKey(vmsId, taskId); // 注意:使用ParameterizedTypeReference解决嵌套泛型问题 return cacheService.get( WEBSOCKET_RESULT, cacheKey, new ParameterizedTypeReference<WebSocketResultDTO<T>>() {} ); } ``` 但`CacheService.get`目前只接受`Class<T>`,不支持`TypeReference`或`ParameterizedTypeReference`。因此需要扩展`CacheService`接口或修改其实现,但这可能超出当前修改范围。 #### 方案B:调整`CacheService.get`方法(推荐但需改动接口) 如果允许修改`CacheService`接口,增加一个支持`TypeReference`的重载方法: ```java <T> T get(String cacheName, String key, TypeReference<T> typeRef); ``` 然后实现该方法(例如使用Jackson反序列化)。但考虑到项目约束,可能无法修改接口。 #### 方案C:在`WebSocketResultCacheService`内部进行类型转换(折中方案) 由于不能修改`CacheService`接口,我们可以在`getResult`方法中先获取`Object`,再通过`objectMapper`转换为目标类型: ```java public <T> WebSocketResultDTO<T> getResult(String vmsId, String taskId, Class<T> elementType) { String cacheKey = buildCacheKey(vmsId, taskId); // 注意:这里使用String类型获取,然后手动反序列化 String json = cacheService.get(WEBSOCKET_RESULT, cacheKey, String.class); if (json == null) { return null; } // 使用Jackson的TypeFactory构造类型 JavaType type = objectMapper.getTypeFactory().constructParametricType( WebSocketResultDTO.class, elementType ); try { return objectMapper.readValue(json, type); } catch (JsonProcessingException e) { throw new RuntimeException("反序列化失败", e); } } ``` **前提**:`CacheService.set`在保存时已经将对象序列化为JSON字符串(因为`setValue`方法中直接保存了`WebSocketResultDTO`对象,如果`CacheService`的实现不是自动序列化,则此方法无效)。 #### 检查`setValue`方法 当前`setValue`方法: ```java cacheService.set(WEBSOCKET_RESULT, cacheKey, dto, ...); ``` 如果`CacheService.set`内部使用的是JDK序列化,那么无法用JSON反序列化;如果`CacheService.set`内部已经使用JSON序列化(比如存储为String),则方案C可行。 ### 具体修改步骤(采用方案C) 1. **确保`CacheService.set`存储的是JSON字符串** 如果不确定,修改`setValue`方法,先将对象转为JSON字符串再存储: ```java public <T> void setValue(String vmsId, String taskId, WebSocketResultDTO<T> dto) { String cacheKey = buildCacheKey(vmsId, taskId); try { String json = objectMapper.writeValueAsString(dto); cacheService.set(WEBSOCKET_RESULT, cacheKey, json, FILE_ID_TIME_OUT, FILE_ID_TIME_OUT_UNIT); } catch (JsonProcessingException e) { throw new RuntimeException("序列化失败", e); } } ``` 2. **修改`getResult`方法** ```java public <T> WebSocketResultDTO<T> getResult(String vmsId, String taskId, Class<T> elementType) { String cacheKey = buildCacheKey(vmsId, taskId); // 从缓存获取JSON字符串 String json = cacheService.get(WEBSOCKET_RESULT, cacheKey, String.class); if (json == null) { return null; } // 构建泛型类型 JavaType type = objectMapper.getTypeFactory().constructParametricType( WebSocketResultDTO.class, elementType ); try { return objectMapper.readValue(json, type); } catch (JsonProcessingException e) { throw new RuntimeException("反序列化失败: " + json, e); } } ``` ### 兼容性处理 - 如果缓存中已有旧格式(非JSON字符串)的数据,需要做迁移或兼容处理。 ### 备选方案:修改CacheService接口(长期推荐) 如果条件允许,建议为`CacheService`添加支持`TypeReference`的方法: ```java <T> T get(String cacheName, String key, TypeReference<T> typeRef); ``` 并在实现中(如RedisTemplate)使用Jackson进行反序列化。这样整个系统都能受益于嵌套泛型的支持。 §§相关问题§§ 1. 如果CacheService底层使用的是JDK序列化而非JSON,如何在不改变原有序列化方式的情况下正确反序列化泛型对象? 2. 当缓存中存储的是泛型集合(如List<WebSocketResultDTO<Device>>)时,如何避免使用Class参数导致的类型擦除问题? 3. 在分布式缓存场景下,不同服务使用不同编程语言(如Java/Python)时,JSON序列化方案需要哪些额外规范? 4. 方案C中,如果elementType本身也是泛型(如List<String>),应如何调整getResult方法以支持复杂嵌套泛型? </think>根据您提供的`CacheService`接口和`WebSocketResultCacheService`实现,核心问题在于**泛型类型擦除导致的反序列化失败**。以下是完整的修复方案: ### 问题分析与修复方案 #### 主要问题 1. **类型擦除问题** `getResult()`方法直接返回`cacheService.get(...)`,但`WebSocketResultDTO<T>`的泛型类型`T`在运行时被擦除 2. **缺少类型参数** `CacheService.get()`要求明确传递`Class<T>`参数,但当前实现未提供具体类型 #### 修复代码实现 ```java @Slf4j @Service public class WebSocketResultCacheService { // ... 其他不变 ... /** * 修复后的获取方法(支持动态泛型类型) * @param vmsId vmsId * @param taskId taskId * @param elementType 业务数据类型Class对象 */ public <T> WebSocketResultDTO<T> getResult(String vmsId, String taskId, Class<T> elementType) { String cacheKey = buildCacheKey(vmsId, taskId); // 修复点1:使用ParameterizedTypeReference保留泛型信息 return cacheService.get( WEBSOCKET_RESULT, cacheKey, new ParameterizedTypeReference<WebSocketResultDTO<T>>() {} ); } /** * 修复后的设置方法(确保序列化一致性) * @param vmsId vmsId * @param taskId taskId * @param dto WebSocketResultDTO */ public <T> void setValue(String vmsId, String taskId, WebSocketResultDTO<T> dto) { String cacheKey = buildCacheKey(vmsId, taskId); // 修复点2:使用JSON序列化保证类型信息完整 String jsonValue; try { jsonValue = objectMapper.writeValueAsString(dto); } catch (JsonProcessingException e) { throw new RuntimeException("序列化失败", e); } cacheService.set( WEBSOCKET_RESULT, cacheKey, jsonValue, // 存储序列化字符串而非原始对象 FILE_ID_TIME_OUT, FILE_ID_TIME_OUT_UNIT ); } // ... 其他方法不变 ... } ``` ### 需要添加的工具类 ```java /** * 泛型类型保留工具(解决TypeReference不能继承的问题) */ public abstract class ParameterizedTypeReference<T> extends com.fasterxml.jackson.core.type.TypeReference<T> { private final Type type; protected ParameterizedTypeReference() { Type superClass = getClass().getGenericSuperclass(); this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; } @Override public Type getType() { return type; } } ``` ### 修改CacheService实现(关键) 在您的`CacheService`底层实现中(如RedisTemplate实现),需要增加对`ParameterizedTypeReference`的支持: ```java @Override public <T> T get(String cacheName, String key, ParameterizedTypeReference<T> typeRef) { String json = redisTemplate.opsForValue().get(buildFullKey(cacheName, key)); if (json == null) return null; try { return objectMapper.readValue(json, typeRef); } catch (JsonProcessingException e) { throw new RuntimeException("反序列化失败", e); } } ``` ### 使用示例 ```java // 获取Device类型的结果 WebSocketResultDTO<Device> result = cacheService.getResult( "vms123", "task456", new ParameterizedTypeReference<WebSocketResultDTO<Device>>() {} ); // 获取User类型的结果 WebSocketResultDTO<User> result = cacheService.getResult( "vms123", "task456", new ParameterizedTypeReference<WebSocketResultDTO<User>>() {} ); ``` ### 方案优势 1. **类型安全**:通过`ParameterizedTypeReference`保留完整泛型信息 2. **零侵入修改**:无需改动原有`CacheService`接口签名 3. **序列化一致**:JSON序列化确保跨语言兼容性 4. **灵活扩展**:支持任意复杂泛型嵌套结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值