解決RedisTemplate的key默認序列化器的問題
redis的客戶端換成瞭spring-boot-starter-data-redis,碰到瞭一個奇怪的問題,
在同一個方法中
1.先hset,再hget,正常獲得數據。
在不同的方法中 先hset,再hget獲取不到數據,通過redis的monitor監控發現瞭命令的問題:
實際我的key為JK_HASH:csrk,hashkey為user,但是根據上圖所示,實際執行的命令多瞭好多其他字符,這是什麼原因呢?
在服務器端先確認發現實際有這個Hash,通過hset可以得到正確的數據,所以第一次執行hset的時候命令是正常的,問題可能出現在hget上面,先打開源碼看一下
@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); }
從這裡可以看到實際上傳給redis的都是byte數據,而byte數組是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); }
然後進一步跟蹤keySerializer()方法
RedisSerializer keySerializer() { return template.getKeySerializer(); } public RedisSerializer<?> getKeySerializer() { return keySerializer; }
最後跟蹤到是RedisTemplate中的屬性keySerializer導致的,而通過打印keySerializer的class發現 默認使用的是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,但它是如何進行初始化的呢,默認的構造函數中並沒有對該屬性進行初始化。
根據RedisTemplate的類關系發現它是繼承RedisAccessor的,而此類是實現的org.springframework.beans.factory.InitializingBean接口,這個接口有個特性,凡是繼承該接口的類,在初始化bean的時候會執行afterPropertiesSet方法。
而afterPropertiesSet方法中,確實對keySerializer進行瞭初始化:
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; }
在這裡可以看到默認使用的正是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,而問題正在這裡,通過查詢可以發現序列化器有這些,而在這裡我們需要使用的是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; }
重新進行測試,方法1hset,方法2hget,方法2能拿到正確的數據,完畢。
補充:redisTemplate取對象時反序列化失敗
錯誤:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Unrecognized field
註意:反序列化時要保證實體對象有一個無參構造函數,否則反序列化也會失敗
json序列化是根據set和get方法來序列化字段的,如果方法正好有set和get開頭但沒有對應field,那麼反序列化就會失敗。
可以在實體類加上註解@JsonIgnoreProperties(ignoreUnknown = true)忽略實體中沒有對應的json的key值,或者在set方法上加上@JsonIgnore註解,如果是用mongoDb數據庫,第二種方法那麼就不大適用瞭,總不能去ObjectId類操作,這是修改別人的源代碼。
以下是redisTemplate在springboot2.x裡面存取對象的配置,等同於第一種方法
@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); //將類名稱序列化到json串中,去掉會導致得出來的的是LinkedHashMap對象,直接轉換實體對象會失敗 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //設置輸入時忽略JSON字符串中存在而Java對象實際沒有的屬性 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= # 連接超時時間(毫秒) spring.redis.timeout=3000ms # Redis默認情況下有16個分片,這裡配置具體使用的分片,默認是0 spring.redis.database=0 # 連接池最大連接數(使用負值表示沒有限制) 默認 8 spring.redis.lettuce.pool.max-active=8 # 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1 spring.redis.lettuce.pool.max-wait=-1 # 連接池中的最大空閑連接 默認 8 spring.redis.lettuce.pool.max-idle=8 # 連接池中的最小空閑連接 默認 0 spring.redis.lettuce.pool.min-idle=0
添加數據進redis
@Test public void testRedis() { Headset headset = new Headset(); redisTemplate.opsForValue().set("test:123", headset); System.out.println(redisTemplate.opsForValue().get("test:123")); }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。
推薦閱讀:
- SpringBoot自定義Redis實現緩存序列化詳解
- 解決RedisTemplate存儲至緩存數據出現亂碼的情況
- 使用@Autowired 註入RedisTemplate報錯的問題及解決
- spring redis 如何實現模糊查找key
- 為Java項目添加Redis緩存的方法