谈谈spring-data-redis遇到的问题
1. key序列化问题
使用spring-data-redis中的redisTemplate存储key-value,然后使用redis-cli去查询时查询不到相应的key。使用keys *时发现redis中key的前缀多了一些字符
\xac\xed\x00\x05t\x00\x0e
问题关键
使用spring-data-redis,默认情况下使用的是org.springframework.data.redis.serializer.JdkSerializationRedisSerializer来进行序列化key
我们看看关键代码 RedisTemplate
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;
}
解决问题
手动设置key的序列化方式为StringRedisSerializer
看代码
@Bean
public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
2. value 序列化问题
我们在使用无参构造GenericJackson2JsonRedisSerializer序列化对象时,序列化出来的值是这样的
{
"@class": "com.xxx.MenuTask",
"taskId": "1617776640",
"planId": "10000000",
"createTime": [
"java.util.Date",
1500540438240
],
"execDate": [
"java.util.Date",
1500480000000
],
"status": 0,
"totals": {
"@class": "java.util.HashMap",
"35430d2a56ae508f24a76a68e71b9f53": 1500,
"75aa86c5a85f65687c95444655c51de9": 500,
"ab90efa7a0079b88f224a296b211c209": 1000
}
}
这样的json格式使用其他的json序列化时会出现无法解析的错误,如fastjson解析时会抛出异常
com.alibaba.fastjson.JSONException: parse error
at com.alibaba.fastjson.serializer.DateCodec.cast(DateCodec.java:206)
at com.alibaba.fastjson.parser.deserializer.AbstractDateDeserializer.deserialze(AbstractDateDeserializer.java:142)
at com.alibaba.fastjson.parser.deserializer.AbstractDateDeserializer.deserialze(AbstractDateDeserializer.java:19)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:71)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:790)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:595)
主要原因是因为Date格式不正确,应该是一个long类型的数字,而不是一个数组
问题关键
我们来分析一下源码
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {
private final ObjectMapper mapper;
/**
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
*/
public GenericJackson2JsonRedisSerializer() {
this((String) null);
}
public GenericJackson2JsonRedisSerializer(String classPropertyTypeName) {
this(new ObjectMapper());
//------------------------ start ------------------------
mapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName)));
if (StringUtils.hasText(classPropertyTypeName)) {
mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);
} else {
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
}
//------------------------ end ------------------------
}
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
Assert.notNull(mapper, "ObjectMapper must not be null!");
this.mapper = mapper;
}
...
...
}
原因所在地方 // — start — // — end —之间的那段代码 源码的63-69行,这里会将属性的类型写到json中。
解决问题
手动设置一个ObjectMapper,这样redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(new ObjectMapper()));
@Bean
public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(new ObjectMapper()));
return redisTemplate;
}