使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的处理
2019-04-11 14:50
2401 查看
文章目录
- 应用场景
- 序列化问题
- 分析
- 总结
- Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer的区别
- 一、使用Jackson2JsonRedisSerializer序列化反序列化带泛型的List数据
- 二、使用GenericJackson2JsonRedisSerializer序列化反序列化带泛型的List数据
- 四、GenericJackson2JsonRedisSerializer和Jackson2JsonRedisSerializerdo效率
- 总结
应用场景
我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力。在执行DAO类的
select***(),
query***()方法时,先从Redis中查询有没有缓存数据,如果有则直接从Redis拿到结果,如果没有再向数据库发起查询请求取数据。
序列化问题
要把
对象做为
key-value对保存在redis中,就必须要解决对象的序列化问题。Spring Data Redis给我们提供了一些现成的方案:
-
JdkSerializationRedisSerializer
. 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。 -
Jackson2JsonRedisSerializer
. 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。 -
GenericJackson2JsonRedisSerializer
. 和Jackson2JsonRedisSerializer
类似。但是它不需要提供序列化对象的类型信息。
分析
如果用方案一,就必须付出缓存多占用4倍内存的代价,实在承受不起。
如果用方案二,则必须给每一种domain对象都配置一个Serializer,即如果我的应用里有100种domain对象,那就必须在spring配置文件中配置100个Jackson2JsonRedisSerializer,这显然也是不现实的。
如果用方案三,就是为了解决Jackson必须提供类型信息的问题,可以同时支持多种不同类型的domain对象。
总结
所以选择使用
GenericJackson2JsonRedisSerializer来配置序列化。
@Bean public RedisCacheManager jsonCacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); }
Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer的区别
一、使用Jackson2JsonRedisSerializer序列化反序列化带泛型的List数据
1、使用Jackson2JsonRedisSerializer序列化value的代码
redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
User user = new User(); user.setUserId(1); user.setUsername("张三"); List<User> userList = new ArrayList<>(); userList.add(user); //不能直接将对象存储进redis中否则在进行反序列化的时候会报 // java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.lx.entity.User错误 //可以先转为json字符串再进行存储 String value = JSON.toJSONString(userList); redisTemplate.opsForValue().set("jackson2JsonRedisSerializer", value, 5, TimeUnit.MINUTES);
2、使用Jackson2JsonRedisSerializer序列化后的数据形式
[ { "userId":1, "username":"张三" } ]
3、使用Jackson2JsonRedisSerializer反序列化时报错
List<User> userListRedis = redisTemplate.opsForValue().get("jackson2JsonRedisSerializer"); userListRedis.forEach(u -> System.out.println(JSON.toJSONString(u)));
错误信息
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.lx.entity.User
- 原因: 序列化带泛型的数据时,会以map的结构进行存储,反序列化时不能将map解析成对象。
4、解决方案:序列化存储时,转成JSON字符串
使用
jackson或者
fastjson都可以,我这里使用的是
fastjson,
需要的依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
Us 20000 er user = new User(); user.setUserId(1); user.setUsername("张三"); List<User> userList = new ArrayList<>(); userList.add(user); redisTemplate.opsForValue().set("jackson2JsonRedisSerializer", JSON.toJSONString(userList), 5, TimeUnit.MINUTES); String res = (String) redisTemplate.opsForValue().get("jackson2JsonRedisSerializer"); JSON.parseArray(res, User.class).forEach(u -> System.out.println(JSON.toJSONString(u)));
二、使用GenericJackson2JsonRedisSerializer序列化反序列化带泛型的List数据
1、使用GenericJackson2JsonRedisSerializer序列化value的代码
redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
User user = new User(); user.setUserId(1); user.setUsername("李四"); List<User> userList = new ArrayList<>(); userList.add(user); redisTemplate.opsForValue().set("genericJackson2JsonRedisSerializer",userList,5,TimeUnit.MINUTES);
2、使用GenericJackson2JsonRedisSerializer序列化后的数据形式
[ "java.util.ArrayList", [ { "@class": "com.lx.entity.User", "userId": 1, "username": "李四" } ] ]
3、使用GenericJackson2JsonRedisSerializer可以正常反序列化
List<User> userListRedis = (List<User>) redisTemplate.opsForValue().get("genericJackson2JsonRedisSerializer"); userListRedis.forEach(u -> System.out.println(JSON.toJSONString(u)));
4、可以正常反序列化的原因
使用
GenericJackson2JsonRedisSerializer序列化时,会保存序列化的对象的包名和类名,反序列化时以这个作为标示就可以反序列化成指定的对象。
5、也可以以JSON字符串保存
redisTemplate.opsForValue().set("genericJackson2JsonRedisSerializer",JSON.toJSONString(userList),5,TimeUnit.MINUTES); List<User> userListRedis = (List<User>) redisTemplate.opsForValue().get("genericJackson2JsonRedisSerializer"); userListRedis.forEach(u -> System.out.println(JSON.toJSONString(u)));
四、GenericJackson2JsonRedisSerializer和Jackson2JsonRedisSerializerdo效率
User user = new User(); user.setUserId(1); user.setUsername("李四"); List<User> userList = new ArrayList<>(); for (int i = 0; i < 10000; i++) { userList.add(user); } redisTemplate.opsForValue().set("serializer", JSON.toJSONString(userList),5,TimeUnit.MINUTES); long end = System.currentTimeMillis(); System.out.println("Serializer序列化需要的时间:" + (end - start));
测试后:
jackson2JsonRedisSerializer序列化需要的时间:687 genericJackson2JsonRedisSerializer序列化需要的时间:22
总结
-
使用
Jackson2JsonRedisSerializer
需要指明序列化的类Class,可以使用Obejct.class
-
使用
GenericJackson2JsonRedisSerializer
和Jackson2JsonRedisSerializerdo
都可以正常序列化非泛型数组对象。GenericJackson2JsonRedisSerializer
也可以正常反序列化非泛型数组对象,但是Jackson2JsonRedisSerializerdo
因为“序列化带泛型的数据时,会以map的结构进行存储,反序列化时不能将map解析成对象”,所以不能反序列化,解决办法: 存储以JSON字符串存储 -
使用
GenericJacksonRedisSerializer
比Jackson2JsonRedisSerializer
效率高 -
GenericJacksonRedisSerializer
和Jackson2JsonRedisSerializer
都是以JSON格式去存储数据,都可以作为Redis的序列化方式
demo测试地址
参考
https://blog.csdn.net/neosmith/article/details/46800235
https://blog.csdn.net/bai_bug/article/details/81222519
相关文章推荐
- 使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决
- 使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决
- [导入]在SYBASE数据库中使用游标(Cursors)将多行查询结果进行逐行处理
- 项目问题反馈:对后台查询的结果列表进行处理:计算平均值,改变小数点位数
- SQL SERVER2005 使用<>与!= 查询结果不一样的问题
- 在使用Hibernate时,因为一个查询需要更多的表连接而要使用SQL来解决性能问题。然而返回的结果集中包含了没有映射的Entity类中的表字段,在这个SQL中还有使用如何将层次关系的父子结点显示为横行
- mybatis的sql中使用concat查询结果是乱码处理
- MyBatis(三):数据库查询结果不为空,但是使用MyBatis框架查询为空问题
- LAMP开发精要(13):PHP中使用mysql_stmt(预处理语句)处理select查询结果
- PL/SQL编辑数据"这些查询结果不可更新,请包括ROWID或使用SELECT...FOR UPDATE获得可更新结果"处理
- VS2008下使用Linq To Entity的Skip().Take()分页查询时遇到数据结果不对的问题
- 格式化查询+使用伪列level+使用level和lpad()函数对层次化查询结果进行格式化处理
- 如何处理“使用 JSON JavaScriptSerializer 进行序列化或反序列化时出错”的问题
- Hibernate中使用sql查询结果后再封装成实体类的问题
- php中使用mysql_stmt(预处理语句)来处理select查询结果
- ADF:如何使用VO处理查询条件的值属于某一动态值列表的问题
- oracle使用LEFT JOIN关联产生的问题在查询结果中使用CASE WHEN 无法判断
- mysqli_stmt类:使用预处理语句处理SELECT查询结果
- 【转】PL/SQL编辑数据"这些查询结果不可更新,请包括ROWID或使用SELECT...FOR UPDATE获得可更新结果"处理
- 使用光标FOR循环处理查询结果集