解決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。如有錯誤或未考慮完全的地方,望不吝賜教。

推薦閱讀: