研究原因:
springboot中使用redis获取数据后,转换成对象时,
总是提示:“com.alibaba.fastjson.JSONException: syntax error, expect {, actual [, pos 0”,
最后发现是因为redis的序列化方式引起的。所以就对redis序列化的方式做一下分析:
1、springboot中redis存储数据序列化方式,常用的有以下几种:
- StringRedisSerializer:将数据序列化成字符串
- Jackson2JsonRedisSerializer:将数据序列化成json
- GenericJackson2JsonRedisSerializer:将数据序列化成json
- JdkSerializationRedisSerializer:将数据序列化对象
2、粗略研究一下各个序列化方式的优缺点:
- 对于StringRedisSerializer,是最基础的,如果所有的数据都是字符串,则优先选用该种序列化方式。springboot提供的StringRedisTemplate默认的就是使用该序列化方式。注意,该种序列化方式只能保存字符串,不能保存对象
- Jackson2JsonRedisSerializer,能保存对象,将对象序列化为json,但是在初始化的时候需要指定要序列化的类,可以使用Object.class。
- GenericJackson2JsonRedisSerializer,与Jackson2JsonRedisSerializer功能相同,只是存在性能方面的差别,以及GenericJackson2JsonRedisSerializer在初始化时不用指定要泛化的类
- JdkSerializationRedisSerializer,将数据序列化成对象,但是要保存的数据对象必须要实现Serializable,否则会提示没有序列化。该种序列化方式再redis中是以二进制的方式存储,所以可读性非常差。springboot中提供的RedisTemplate就默认使用该种序列化方式。如果想有好的可读性,就需要更改它的序列化方式
3、各序列化方式的性能优劣:
网上很多人说优先推荐GenericJackson2JsonRedisSerializer,是因为Jackson2JsonRedisSerializer需要指定要泛化的类,下面对Jackson2JsonRedisSerializer,GenericJackson2JsonRedisSerializer,JdkSerializationRedisSerializer三个序列化方式的序列化和反序列化能力做了一个测试:
@Slf4j
public class main {
public static void main(String[] args) {
JdkSerializationRedisSerializer jdkSeri = new JdkSerializationRedisSerializer();
GenericJackson2JsonRedisSerializer genJsonSeri = new GenericJackson2JsonRedisSerializer();
Jackson2JsonRedisSerializer jack2Seri = new Jackson2JsonRedisSerializer(Object.class);
User user = new User("1","tom",20,"boy");
List<Object> list = new ArrayList<>();
for(int i = 0; i < 1000; i++){
list.add(user);
}
log.info("jdkSeri序列化开始");
long jdkSeri_Start = System.currentTimeMillis();
byte[] serialize = jdkSeri.serialize(list);
log.info("jdkSeri序列化结束,耗时:{}ms,序列化之后的长度为:{}==============",(System.currentTimeMillis()-jdkSeri_Start),serialize.length);
log.info("jdkSeri开始反序列化");
long jdkSeri_Start_again = System.currentTimeMillis();
jdkSeri.deserialize(serialize);
log.info("jdkSeri反序列化耗时:{}ms===========",(System.currentTimeMillis()-jdkSeri_Start_again));
log.info("genJsonSeri序列化开始");
long genJsonSeri_Start = System.currentTimeMillis();
byte[] serialize1 = genJsonSeri.serialize(list);
log.info("genJsonSeri序列化结束,耗时:{}ms,序列化之后的长度为:{}==============",(System.currentTimeMillis()-genJsonSeri_Start),serialize1.length);
log.info("genJsonSeri开始反序列化");
long genJsonSeri_Start_again = System.currentTimeMillis();
genJsonSeri.deserialize(serialize1);
log.info("genJsonSeri反序列化耗时:{}ms===========",(System.currentTimeMillis()-genJsonSeri_Start_again));
log.info("jack2Seri序列化开始");
long jack2Seri_Start = System.currentTimeMillis();
byte[] serialize2 = jack2Seri.serialize(list);
log.info("jack2Seri序列化结束,耗时:{}ms,序列化之后的长度为:{}===============",(System.currentTimeMillis()-jack2Seri_Start),serialize2.length);
log.info("jack2Seri开始反序列化");
long jack2Seri_Start_again = System.currentTimeMillis();
jack2Seri.deserialize(serialize2);
log.info("jack2Seri反序列化耗时:{}ms=============",(System.currentTimeMillis()-jack2Seri_Start_again));
}
}
16:29:32.710 [main] INFO com.whj.springboot.config.main - jdkSeri序列化开始
16:29:32.790 [main] INFO com.whj.springboot.config.main - jdkSeri序列化结束,耗时:68ms,序列化之后的长度为:5175==============
16:29:32.798 [main] INFO com.whj.springboot.config.main - jdkSeri开始反序列化
16:29:32.977 [main] INFO com.whj.springboot.config.main - jdkSeri反序列化耗时:179ms===========
16:29:32.977 [main] INFO com.whj.springboot.config.main - genJsonSeri序列化开始
16:29:33.237 [main] INFO com.whj.springboot.config.main - genJsonSeri序列化结束,耗时:260ms,序列化之后的长度为:87025==============
16:29:33.237 [main] INFO com.whj.springboot.config.main - genJsonSeri开始反序列化
16:29:33.428 [main] INFO com.whj.springboot.config.main - genJsonSeri反序列化耗时:191ms===========
16:29:33.428 [main] INFO com.whj.springboot.config.main - jack2Seri序列化开始
16:29:33.436 [main] INFO com.whj.springboot.config.main - jack2Seri序列化结束,耗时:7ms,序列化之后的长度为:45001===============
16:29:33.436 [main] INFO com.whj.springboot.config.main - jack2Seri开始反序列化
16:29:33.451 [main] INFO com.whj.springboot.config.main - jack2Seri反序列化耗时:15ms=============
网上很多人分析说推荐使用GenericJackson2JsonRedisSerializer,但是通过上面的测试,我们可以得出结论:
1、就序列化和反序列化性能来说,Jackson2JsonRedisSerializer方式效率是最高的,同时在redis中也有很好的可读性
2、就占用存储空间来说,JdkSerializationRedisSerializer方式序列化之后长度是最小的(因为是用二进制方式存储)
相反,GenericJackson2JsonRedisSerializer方式没发现什么亮眼的特点(也可能我也是一知半解,欢迎讨论)
4、问题回答
回到我初始的问题,因为我的ValueSerializer使用的是StringRedisSerializer,在保存的时候将对象转换成了字符串保存,这样在redis中就是当做了普通的字符串,而不是对象。此时取出数据,使用fastJson转换成对象时,就会报上面的问题,因为它不是对象数据,而是个字符串。
5、JSON转换成对象
1、将json数据转换成对象
1、使用com.alibaba.fastjson
2、User类中不管是普通属性还是对象属性或者集合属性,都能直接转换成对象
JSON.parseObject(str,User.class)
2、 将带有泛型的数据转换成对象,同样使用fastjson
1、使用com.alibaba.fastjson
2、带泛型的都需要使用TypeReference
JSON.parseObject(s,new TypeReference<BaseResult<User>>(){})
6、修改RedisTemplate序列化方式
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
/**
* 配置自己的redisTemplate
* StringRedisTemplate 默认使用使用StringRedisSerializer来序列化
* RedisTemplate 默认使用JdkSerializationRedisSerializer来序列化
*/
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//开启默认类型
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"));
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}