使用stringRedisTemplate操作redis hash结构数据只能存储String类型的问题

本文介绍如何使用StringRedisTemplate正确地存储和检索自定义对象,如UserPO,并通过配置序列化方式来避免类型转换错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在学习redis,但是在使用stringRedisTemplate模板的时候发现只能存储string的类型。
private StringRedisTemplate stringRedisTemplate;

    @Autowired(required = false)
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public void saveUserPO(UserPO user) {
        BoundHashOperations<String, String, UserPO> ops = stringRedisTemplate
                .boundHashOps(CommonConstants.USER);
        ops.put(user.getUserId(), user);
    }

我想要存储UserPO 类型的对象但是发现报错了:
这里写图片描述
但是我在这里明明使用的是泛型 , 规定只能存储UserPO类型的啊,居然报错了,真的让人费解啊。你可能马上想到我把它format成String不行了吗?

@Override
    public void saveUserPO(UserPO user) {
        BoundHashOperations<String, String, String> ops = stringRedisTemplate
                .boundHashOps(CommonConstants.USER);
        ops.put(user.getUserId(), JSON.toJSONString(user));
    }

这样是可以存进去,但是取出的json list中的对象都是UserPO的String类型,显然这不是我们要得json。
经过一番努力后发现StringRedisTemplate类的构造函数是这样的

public StringRedisTemplate() {
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        this.setKeySerializer(stringSerializer);
        this.setValueSerializer(stringSerializer);
        this.setHashKeySerializer(stringSerializer);
        this.setHashValueSerializer(stringSerializer);
    }

原来在使用StringRedisSerializer 操作redis数据类型的时候必须要set相对应的序列化,这里源码是默认的String,所以我要更改它默认的序列化

@Autowired(required = false)
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(UserPO.class));
        this.stringRedisTemplate = stringRedisTemplate;
    }

加上这行代码:

stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(UserPO.class));

再看看效果:
这里写图片描述
已经存进去了,希望对你们有帮助。

<think>我们使用Java语言将语言表数据存储RedisHash结构中。语言表通常包含多语言键值对,例如每个语言条目有一个唯一的key,然后对应不同语言的翻译值。参考引用[2]和[5]中提到的,我们可以使用RedisHash数据结构来存储对象。在Java中,我们可以使用SpringData Redis提供的`RedisTemplate`或者`StringRedisTemplate`来操作Redis。根据引用[5]中的示例,我们可以使用`BoundHashOperations`来绑定一个特定的Hash key,然后进行读写操作。步骤:1.配置Redis连接和RedisTemplate(通常使用StringRedisTemplate,因为语言表数据一般是字符串键值对)。2.设计Redis存储语言表的Hash的key,例如:`language:translations`。3.将语言表数据(例如一个Map,其中键是语言条目的key,值是对应的翻译文本)存储RedisHash中。注意:引用[2]提到,为了节省内存,我们使用String序列化器,手动完成对象序列化和反序列化。但这里我们存储的是字符串,所以可以直接存储。示例代码:假设我们有一个语言表的数据结构如下:-键:消息代码(如:login.title)-值:对应的翻译文本(如:{"en":"Login","zh":"登录"})或者,如果我们按语言分不同的Hash,那么每个Hash的field是消息代码,value是翻译文本。但是根据问题描述,我们是要将整个语言表存储到一个Hash中,那么我们可以这样设计:-Redis Hash的key:我们定义为`language:translations`-Hash的field:消息代码(例如:login.title)- Hash的value:可以是JSON字符串,包含多种语言的翻译(如:{"en":"Login", "zh":"登录"})或者,如果只存储一种语言的翻译,那么value就是该语言的文本。这里我们按照多语言存储,即一个消息代码对应多种语言的翻译,所以value使用JSON字符串。然而,根据引用[4]和[5],我们也可以考虑将不同语言分开存储到不同的Hash中,例如:-英文:`language:en`,field为消息代码,value为英文翻译-中文:`language:zh`,field为消息代码,value为中文翻译但用户要求是存储Hash存储结构中,并且没有明确说明是分开还是合在一起。根据问题,我们假设是合在一个Hash中,每个field对应一个消息代码,value是一个包含所有语言的JSON字符串。但是,这样设计在更新某一种语言的时候需要读取整个JSON,修改后再写入,不太方便。另一种方案是使用多个Hash,每个语言一个Hash。这里我们先按照一个Hash来设计,因为用户要求是“一个Hash存储结构”。不过,如果语言表数据量很大,一个Hash可能会很大,影响性能。所以,我们也可以考虑按语言分不同的Hash,这样每个Hash存储一种语言的所有翻译。具体选择取决于业务需求。由于问题没有明确,我们提供两种方案:方案一:所有语言存储在一个Hash中,每个field是一个消息代码,value是该消息代码对应的所有语言的JSON字符串。方案二:每种语言一个Hash,每个Hash的field是消息代码,value是单一语言的翻译文本。这里我们按照方案一实现,因为用户要求是“将语言表缓存到RedisHash存储结构中”,这里“语言表”可能是指整个表。实现步骤:1.创建Redis配置,注入StringRedisTemplate。2.编写一个方法,将语言表数据(Map<String,Map<String, String>>)存储Redis的一个Hash中。其中,外层Map的key是消息代码,内层Map的key是语言代码(如en,zh),value是翻译文本。3.将每个消息代码对应的内层Map转换为JSON字符串,然后存入Hash。示例代码:假设我们有一个语言表服务类:```javaimportorg.springframework.data.redis.core.StringRedisTemplate; importorg.springframework.stereotype.Repository;import com.fasterxml.jackson.core.JsonProcessingException; importcom.fasterxml.jackson.databind.ObjectMapper; importjava.util.Map;@Repositorypublicclass LanguageCacheRepository{private finalStringRedisTemplate redisTemplate;privatefinal ObjectMapper objectMapper;//定义Redis存储语言表的Hash的keyprivate staticfinal StringLANGUAGE_HASH_KEY= "language:translations";public LanguageCacheRepository(StringRedisTemplate redisTemplate){this.redisTemplate= redisTemplate;this.objectMapper= newObjectMapper();}/***缓存整个语言表*@paramlanguageMap语言表数据,结构为:消息代码-> (语言代码->翻译文本)*/public voidcacheAllLanguageData(Map<String, Map<String,String>>languageMap) {languageMap.forEach((messageCode,translations)-> {try{//将每种语言翻译的Map转换为JSON字符串String value= objectMapper.writeValueAsString(translations);//存储RedisHashredisTemplate.opsForHash().put(LANGUAGE_HASH_KEY,messageCode, value);} catch(JsonProcessingExceptione){throw newRuntimeException("Error serializing translationsfor messagecode:" +messageCode, e);}});}/***根据消息代码获取该消息的所有语言翻译* @param messageCode消息代码* @return Map<String,String>语言代码到翻译文本的映射*/public Map<String,String>getTranslationsByMessageCode(String messageCode){String value= (String)redisTemplate.opsForHash().get(LANGUAGE_HASH_KEY,messageCode);if (value ==null){return null;}try {returnobjectMapper.readValue(value,Map.class);}catch (JsonProcessingException e) {thrownew RuntimeException("Errordeserializing translationsfor messagecode:" +messageCode, e);}} }```注意:这里使用了Jackson库将Map转换为JSON字符串。确保项目中引入了Jackson依赖。但是,如果语言表数据量很大,一次性存储所有数据可能会造成Redis阻塞。我们可以考虑使用管道(pipeline)来批量操作,提高效率。使用管道优化: ```javapublicvoid cacheAllLanguageDataWithPipeline(Map<String,Map<String, String>> languageMap){redisTemplate.executePipelined((RedisCallback<Object>)connection ->{languageMap.forEach((messageCode, translations) ->{try {Stringvalue =objectMapper.writeValueAsString(translations);connection.hSet(LANGUAGE_HASH_KEY.getBytes(),messageCode.getBytes(), value.getBytes());} catch(JsonProcessingExceptione){throw newRuntimeException("Errorserializingtranslations formessage code: "+ messageCode,e);}});returnnull;});}```另外,我们也可以设置过期时间,但语言表通常是很少变化的,可以设置较长的过期时间或者不设置,然后在更新语言表时主动刷新缓存。如果按照方案二(按语言分开存储)实现,则:定义Hash的key为:`language:translations:<language_code>`,例如`language:translations:en`。代码调整: ```java/***缓存整个语言表(按语言分开存储)* @param languageMap语言表数据,结构为:语言代码-> (消息代码->翻译文本)*/public voidcacheAllLanguageDataByLang(Map<String, Map<String,String>>languageMap) {languageMap.forEach((langCode,translations)-> {StringhashKey= "language:translations:" +langCode;translations.forEach((messageCode,text)-> {redisTemplate.opsForHash().put(hashKey, messageCode,text);});});}```但是,用户要求是存储到“一个Hash存储结构”,所以我们优先采用方案一。总结:我们使用一个Hash存储整个语言表,其中每个field是消息代码,value是JSON字符串(包含所有语言的翻译)。同时提供了使用管道来批量存储的方法以提高性能。注意:在实际应用中,需要根据语言表的大小和访问模式来选择最合适的存储方式。相关问题:1.如何更新单个消息代码的多语言翻译?2.如果语言表数据量非常大,使用一个Hash存储是否有性能问题?如何优化?3.如何实现按语言分页查询语言表数据?4.如何设置语言表缓存的过期时间?5.当语言表更新时,如何保证缓存与数据库的一致性?</think>### Java 实现语言表数据缓存到 Redis Hash解决方案 #### 核心实现思路 1. **数据结构设计**:使用 Redis Hash 存储语言表数据,其中: - Hash key:`language:translations` - Field:语言代码(如 `en`, `zh`) - Value:JSON 格式的语言键值对 2. **序列化策略**:使用 String 序列化器,手动处理 Java 对象与 JSON 的转换[^2] 3. **缓存管理**:设置合理过期时间,提供批量操作接口 #### 完整代码实现 ```java import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.BoundHashOperations; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; import java.util.concurrent.TimeUnit; public class LanguageCacheService { private final StringRedisTemplate redisTemplate; private final ObjectMapper objectMapper; private static final String CACHE_KEY = "language:translations"; public LanguageCacheService(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; this.objectMapper = new ObjectMapper(); } /** * 缓存整个语言表 * @param languageMap 语言数据 Map<语言代码, Map<键, 翻译>> */ public void cacheAllLanguages(Map<String, Map<String, String>> languageMap) { BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(CACHE_KEY); languageMap.forEach((langCode, translations) -> { try { // 将翻译Map转为JSON字符串 String jsonValue = objectMapper.writeValueAsString(translations); hashOps.put(langCode, jsonValue); } catch (Exception e) { throw new RuntimeException("JSON序列化失败: " + langCode, e); } }); // 设置24小时过期时间 hashOps.expire(24, TimeUnit.HOURS); } /** * 获取指定语言的翻译Map * @param langCode 语言代码 (如 "en", "zh") * @return 翻译键值对Map */ public Map<String, String> getLanguageTranslations(String langCode) { BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(CACHE_KEY); String jsonValue = hashOps.get(langCode); if (jsonValue == null) return null; try { return objectMapper.readValue( jsonValue, objectMapper.getTypeFactory().constructMapType( Map.class, String.class, String.class ) ); } catch (Exception e) { throw new RuntimeException("JSON反序列化失败: " + langCode, e); } } /** * 更新单种语言数据 * @param langCode 语言代码 * @param translations 新的翻译数据 */ public void updateLanguage(String langCode, Map<String, String> translations) { BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(CACHE_KEY); try { String jsonValue = objectMapper.writeValueAsString(translations); hashOps.put(langCode, jsonValue); } catch (Exception e) { throw new RuntimeException("更新语言数据失败: " + langCode, e); } } /** * 删除指定语言缓存 * @param langCode 语言代码 */ public void deleteLanguage(String langCode) { redisTemplate.boundHashOps(CACHE_KEY).delete(langCode); } } ``` #### 使用示例 ```java // 初始化服务 StringRedisTemplate redisTemplate = ... // 通过Spring注入 LanguageCacheService cacheService = new LanguageCacheService(redisTemplate); // 准备语言数据 Map<String, Map<String, String>> languageData = Map.of( "en", Map.of("welcome", "Hello", "goodbye", "Goodbye"), "zh", Map.of("welcome", "你好", "goodbye", "再见"), "es", Map.of("welcome", "Hola", "goodbye", "Adiós") ); // 缓存到Redis cacheService.cacheAllLanguages(languageData); // 获取中文翻译 Map<String, String> zhTranslations = cacheService.getLanguageTranslations("zh"); System.out.println(zhTranslations.get("welcome")); // 输出: 你好 // 更新英文翻译 cacheService.updateLanguage("en", Map.of("welcome", "Welcome", "goodbye", "See you")); ``` #### 关键优化策略 1. **批量操作优化**: ```java public void batchUpdate(Map<String, Map<String, String>> updates) { redisTemplate.executePipelined((RedisCallback<Object>) connection -> { updates.forEach((langCode, translations) -> { try { String json = objectMapper.writeValueAsString(translations); connection.hSet(CACHE_KEY.getBytes(), langCode.getBytes(), json.getBytes()); } catch (Exception e) { // 异常处理 } }); return null; }); } ``` 2. **缓存预热机制**: ```java @PostConstruct public void preloadCache() { if (!redisTemplate.hasKey(CACHE_KEY)) { Map<String, Map<String, String>> defaultData = languageService.loadDefaultLanguages(); cacheAllLanguages(defaultData); } } ``` 3. **内存压缩优化**(大文本场景): ```java private String compress(String data) { // 使用GZIP压缩实现 // 详见引用[4]中的压缩技术 } private String decompress(byte[] compressed) { // GZIP解压实现 } ``` #### 缓存架构设计 ```mermaid graph LR A[应用服务] --> B[LanguageCacheService] B --> C{Redis Hash} C --> D[语言包en] C --> E[语言包zh] C --> F[语言包es] B --> G[JSON序列化] G --> H[压缩处理] ``` #### 最佳实践建议 1. **数据结构**:优先使用 Hash 存储多语言数据,内存效率高[^1] 2. **序列化**:使用 String 序列化器+JSON 格式保证兼容性[^2] 3. **过期策略**:设置 24-72 小时过期时间,配合预热机制[^4] 4. **更新策略**:采用增量更新(`HSET`)而非全量覆盖 5. **监控指标**:监控缓存命中率和内存使用量 6. **异常处理**:添加 JSON 序列化/反序列化的健壮错误处理 > 实现参考 Redis 内存优化[^1]、序列化策略[^2]和缓存实战[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值