解決RedisTemplate的key默認(rèn)序列化器的問(wèn)題
redis的客戶(hù)端換成了spring-boot-starter-data-redis,碰到了一個(gè)奇怪的問(wèn)題,
在同一個(gè)方法中
1.先hset,再hget,正常獲得數(shù)據(jù)。
在不同的方法中 先hset,再hget獲取不到數(shù)據(jù),通過(guò)redis的monitor監(jiān)控發(fā)現(xiàn)了命令的問(wèn)題:
實(shí)際我的key為JK_HASH:csrk,hashkey為user,但是根據(jù)上圖所示,實(shí)際執(zhí)行的命令多了好多其他字符,這是什么原因呢?
在服務(wù)器端先確認(rèn)發(fā)現(xiàn)實(shí)際有這個(gè)Hash,通過(guò)hset可以得到正確的數(shù)據(jù),所以第一次執(zhí)行hset的時(shí)候命令是正常的,問(wèn)題可能出現(xiàn)在hget上面,先打開(kāi)源碼看一下
@SuppressWarnings("unchecked") public HV get(K key, Object hashKey) { final byte[] rawKey = rawKey(key); final byte[] rawHashKey = rawHashKey(hashKey); byte[] rawHashValue = execute(new RedisCallback<byte[]>() { public byte[] doInRedis(RedisConnection connection) { return connection.hGet(rawKey, rawHashKey); } }, true); return (HV) deserializeHashValue(rawHashValue); }
從這里可以看到實(shí)際上傳給redis的都是byte數(shù)據(jù),而byte數(shù)組是rawKey和rawHashKey生成的,先看下rawKey方法
@SuppressWarnings("unchecked") byte[] rawKey(Object key) { Assert.notNull(key, "non null key required"); if (keySerializer() == null && key instanceof byte[]) { return (byte[]) key; } return keySerializer().serialize(key); }
然后進(jìn)一步跟蹤keySerializer()方法
RedisSerializer keySerializer() { return template.getKeySerializer(); } public RedisSerializer<?> getKeySerializer() { return keySerializer; }
最后跟蹤到是RedisTemplate中的屬性keySerializer導(dǎo)致的,而通過(guò)打印keySerializer的class發(fā)現(xiàn) 默認(rèn)使用的是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,但它是如何進(jìn)行初始化的呢,默認(rèn)的構(gòu)造函數(shù)中并沒(méi)有對(duì)該屬性進(jìn)行初始化。
根據(jù)RedisTemplate的類(lèi)關(guān)系發(fā)現(xiàn)它是繼承RedisAccessor的,而此類(lèi)是實(shí)現(xiàn)的org.springframework.beans.factory.InitializingBean接口,這個(gè)接口有個(gè)特性,凡是繼承該接口的類(lèi),在初始化bean的時(shí)候會(huì)執(zhí)行afterPropertiesSet方法。
而afterPropertiesSet方法中,確實(shí)對(duì)keySerializer進(jìn)行了初始化:
public void afterPropertiesSet() { super.afterPropertiesSet(); boolean defaultUsed = false; if (defaultSerializer == null) { defaultSerializer = new JdkSerializationRedisSerializer( classLoader != null ? classLoader : this.getClass().getClassLoader()); } if (enableDefaultSerializer) { if (keySerializer == null) { keySerializer = defaultSerializer; defaultUsed = true; } if (valueSerializer == null) { valueSerializer = defaultSerializer; defaultUsed = true; } if (hashKeySerializer == null) { hashKeySerializer = defaultSerializer; defaultUsed = true; } if (hashValueSerializer == null) { hashValueSerializer = defaultSerializer; defaultUsed = true; } } if (enableDefaultSerializer && defaultUsed) { Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized"); } if (scriptExecutor == null) { this.scriptExecutor = new DefaultScriptExecutor<K>(this); } initialized = true; }
在這里可以看到默認(rèn)使用的正是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,而問(wèn)題正在這里,通過(guò)查詢(xún)可以發(fā)現(xiàn)序列化器有這些,而在這里我們需要使用的是StringRedisSerializer
加入如下代碼:
@Autowired(required = false) public void setRedisTemplate(RedisTemplate redisTemplate) { RedisSerializer stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setHashValueSerializer(stringSerializer); this.redisTemplate = redisTemplate; }
重新進(jìn)行測(cè)試,方法1hset,方法2hget,方法2能拿到正確的數(shù)據(jù),完畢。
補(bǔ)充:redisTemplate取對(duì)象時(shí)反序列化失敗
錯(cuò)誤:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Unrecognized field
注意:反序列化時(shí)要保證實(shí)體對(duì)象有一個(gè)無(wú)參構(gòu)造函數(shù),否則反序列化也會(huì)失敗
json序列化是根據(jù)set和get方法來(lái)序列化字段的,如果方法正好有set和get開(kāi)頭但沒(méi)有對(duì)應(yīng)field,那么反序列化就會(huì)失敗。
可以在實(shí)體類(lèi)加上注解@JsonIgnoreProperties(ignoreUnknown = true)忽略實(shí)體中沒(méi)有對(duì)應(yīng)的json的key值,或者在set方法上加上@JsonIgnore注解,如果是用mongoDb數(shù)據(jù)庫(kù),第二種方法那么就不大適用了,總不能去ObjectId類(lèi)操作,這是修改別人的源代碼。
以下是redisTemplate在springboot2.x里面存取對(duì)象的配置,等同于第一種方法
@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); //Use Jackson 2Json RedisSerializer to serialize and deserialize the value of redis (default JDK serialization) Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //將類(lèi)名稱(chēng)序列化到j(luò)son串中,去掉會(huì)導(dǎo)致得出來(lái)的的是LinkedHashMap對(duì)象,直接轉(zhuǎn)換實(shí)體對(duì)象會(huì)失敗 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //設(shè)置輸入時(shí)忽略JSON字符串中存在而Java對(duì)象實(shí)際沒(méi)有的屬性 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); //Use String RedisSerializer to serialize and deserialize the key value of redis RedisSerializer redisSerializer = new StringRedisSerializer(); //key redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); //value redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
application.properties文件添加如下,springboot2.x連接池用的是lettuce
spring.redis.host=localhost spring.redis.password= # 連接超時(shí)時(shí)間(毫秒) spring.redis.timeout=3000ms # Redis默認(rèn)情況下有16個(gè)分片,這里配置具體使用的分片,默認(rèn)是0 spring.redis.database=0 # 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制) 默認(rèn) 8 spring.redis.lettuce.pool.max-active=8 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制) 默認(rèn) -1 spring.redis.lettuce.pool.max-wait=-1 # 連接池中的最大空閑連接 默認(rèn) 8 spring.redis.lettuce.pool.max-idle=8 # 連接池中的最小空閑連接 默認(rèn) 0 spring.redis.lettuce.pool.min-idle=0
添加數(shù)據(jù)進(jìn)redis
@Test public void testRedis() { Headset headset = new Headset(); redisTemplate.opsForValue().set("test:123", headset); System.out.println(redisTemplate.opsForValue().get("test:123")); }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Java實(shí)現(xiàn)單鏈表反轉(zhuǎn)的多種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)單鏈表反轉(zhuǎn)的多種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Java聊天室之實(shí)現(xiàn)獲取Socket功能
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之實(shí)現(xiàn)獲取遠(yuǎn)程服務(wù)器和客戶(hù)機(jī)的IP地址和端口號(hào)功能,文中的示例代碼講解詳細(xì),需要的可以了解一下2022-10-10基于rocketmq的有序消費(fèi)模式和并發(fā)消費(fèi)模式的區(qū)別說(shuō)明
這篇文章主要介紹了基于rocketmq的有序消費(fèi)模式和并發(fā)消費(fèi)模式的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-0614個(gè)編寫(xiě)Spring MVC控制器的實(shí)用小技巧(吐血整理)
這篇文章主要介紹了14個(gè)編寫(xiě)Spring MVC控制器的實(shí)用小技巧(吐血整理),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11解析spring boot與ireport 整合問(wèn)題
本文通過(guò)實(shí)例代碼給大家介紹了spring boot 與 ireport 整合問(wèn)題,關(guān)于pom文件依賴(lài)的問(wèn)題通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-10-10SpringBoot中webSocket實(shí)現(xiàn)即時(shí)聊天
這篇文章主要介紹了SpringBoot中webSocket實(shí)現(xiàn)即時(shí)聊天,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04