解决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") publicHVget(Kkey,ObjecthashKey){ finalbyte[]rawKey=rawKey(key); finalbyte[]rawHashKey=rawHashKey(hashKey); byte[]rawHashValue=execute(newRedisCallback(){ publicbyte[]doInRedis(RedisConnectionconnection){ returnconnection.hGet(rawKey,rawHashKey); } },true); return(HV)deserializeHashValue(rawHashValue); }
从这里可以看到实际上传给redis的都是byte数据,而byte数组是rawKey和rawHashKey生成的,先看下rawKey方法
@SuppressWarnings("unchecked") byte[]rawKey(Objectkey){ Assert.notNull(key,"nonnullkeyrequired"); if(keySerializer()==null&&keyinstanceofbyte[]){ return(byte[])key; } returnkeySerializer().serialize(key); }
然后进一步跟踪keySerializer()方法
RedisSerializerkeySerializer(){ returntemplate.getKeySerializer(); } publicRedisSerializer>getKeySerializer(){ returnkeySerializer; }
最后跟踪到是RedisTemplate中的属性keySerializer导致的,而通过打印keySerializer的class发现默认使用的是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,但它是如何进行初始化的呢,默认的构造函数中并没有对该属性进行初始化。
根据RedisTemplate的类关系发现它是继承RedisAccessor的,而此类是实现的org.springframework.beans.factory.InitializingBean接口,这个接口有个特性,凡是继承该接口的类,在初始化bean的时候会执行afterPropertiesSet方法。
而afterPropertiesSet方法中,确实对keySerializer进行了初始化:
publicvoidafterPropertiesSet(){ super.afterPropertiesSet(); booleandefaultUsed=false; if(defaultSerializer==null){ defaultSerializer=newJdkSerializationRedisSerializer( 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,"defaultserializernullandnotallserializersinitialized"); } if(scriptExecutor==null){ this.scriptExecutor=newDefaultScriptExecutor(this); } initialized=true; }
在这里可以看到默认使用的正是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,而问题正在这里,通过查询可以发现序列化器有这些,而在这里我们需要使用的是StringRedisSerializer
加入如下代码:
@Autowired(required=false) publicvoidsetRedisTemplate(RedisTemplateredisTemplate){ RedisSerializerstringSerializer=newStringRedisSerializer(); 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:CouldnotreadJSON:Unrecognizedfield
注意:反序列化时要保证实体对象有一个无参构造函数,否则反序列化也会失败
json序列化是根据set和get方法来序列化字段的,如果方法正好有set和get开头但没有对应field,那么反序列化就会失败。
可以在实体类加上注解@JsonIgnoreProperties(ignoreUnknown=true)忽略实体中没有对应的json的key值,或者在set方法上加上@JsonIgnore注解,如果是用mongoDb数据库,第二种方法那么就不大适用了,总不能去ObjectId类操作,这是修改别人的源代码。
以下是redisTemplate在springboot2.x里面存取对象的配置,等同于第一种方法
@Configuration @EnableCaching publicclassRedisConfigextendsCachingConfigurerSupport{ @Bean publicRedisTemplateredisTemplate(RedisConnectionFactoryconnectionFactory){ RedisTemplate redisTemplate=newRedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); //UseJackson2JsonRedisSerializertoserializeanddeserializethevalueofredis(defaultJDKserialization) Jackson2JsonRedisSerializerjackson2JsonRedisSerializer=newJackson2JsonRedisSerializer(Object.class); ObjectMapperobjectMapper=newObjectMapper(); 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); //UseStringRedisSerializertoserializeanddeserializethekeyvalueofredis RedisSerializerredisSerializer=newStringRedisSerializer(); //key redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); //value redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); returnredisTemplate; } }
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 publicvoidtestRedis(){ Headsetheadset=newHeadset(); redisTemplate.opsForValue().set("test:123",headset); System.out.println(redisTemplate.opsForValue().get("test:123")); }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。