使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决
2018-02-09 11:32
417 查看
应用场景
我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力。在执行DAO类的select***(),
query***()方法时,先从
Redis中查询有没有缓存数据,如果有则直接从
Redis拿到结果,如果没有再向数据库发起查询请求取数据。
序列化问题
要把domain object做为key-value对保存在redis中,就必须要解决对象的序列化问题。Spring Data Redis给我们提供了一些现成的方案:JdkSerializationRedisSerializer. 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(
class),但缺点是序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。
Jackson2JsonRedisSerializer. 使用
Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(
.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。
如果用方案一,就必须付出缓存多占用4倍内存的代价,实在承受不起。如果用方案二,则必须给每一种domain对象都配置一个Serializer,即如果我的应用里有100种domain对象,那就必须在spring配置文件中配置100个
Jackson2JsonRedisSerializer,这显然是不现实的。
通过google, 发现spring data redis项目中有一个#145 pull request, 而这个提交请求的内容正是解决
Jackson必须提供类型信息的问题。然而不幸的是这个请求还没有被
merge。但我们可以把代码copy一下放到自己的项目中:
/** * @author Christoph Strobl * @since 1.6 */ 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); } /** * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default * {@link JsonTypeInfo.Id#CLASS} will be used. * * @param classPropertyTypeName Name of the JSON property holding type information. Can be {@literal null}. */ public GenericJackson2JsonRedisSerializer(String classPropertyTypeName) { this(new ObjectMapper()); if (StringUtils.hasText(classPropertyTypeName)) { mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName); } else { mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY); } } /** * Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization * process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for * specific types. * * @param mapper must not be {@literal null}. */ public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) { Assert.notNull(mapper, "ObjectMapper must not be null!"); this.mapper = mapper; } /* * (non-Javadoc) * @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object) */ @Override public byte[] serialize(Object source) throws SerializationException { if (source == null) { return SerializationUtils.EMPTY_ARRAY; } try { return mapper.writeValueAsBytes(source); } catch (JsonProcessingException e) { throw new SerializationException("Could not write JSON: " + e.getMessage(), e); } } /* * (non-Javadoc) * @see org.springframework.data.redis.serializer.RedisSerializer#deserialize(byte[]) */ @Override public Object deserialize(byte[] source) throws SerializationException { return deserialize(source, Object.class); } /** * @param source can be {@literal null}. * @param type must not be {@literal null}. * @return {@literal null} for empty source. * @throws SerializationException */ public <T> T deserialize(byte[] source, Class<T> type) throws SerializationException { Assert.notNull(type, "Deserialization type must not be null! Pleaes provide Object.class to make use of Jackson2 default typing."); if (SerializationUtils.isEmpty(source)) { return null; } try { return mapper.readValue(source, type); } catch (Exception ex) { throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex); } } }然后在配置文件中使用这个
GenericJackson2JsonRedisSerializer:
<bean id="jacksonSerializer" class="com.fh.taolijie.component.GenericJackson2JsonRedisSerializer"> </bean>重新构建部署,我们发现这个serializer可以同时支持多种不同类型的domain对象,问题解决。
相关文章推荐
- 使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决
- 在使用Hibernate时,因为一个查询需要更多的表连接而要使用SQL来解决性能问题。然而返回的结果集中包含了没有映射的Entity类中的表字段,在这个SQL中还有使用如何将层次关系的父子结点显示为横行
- 解决mybatis使用char类型字段查询oracle数据库时结果返回null问题
- 数据值为NULL,导致条件查询不到正确结果,ISNULL函数的使用解决问题
- VS2008下使用Linq To Entity的Skip().Take()分页查询时遇到数据结果不对的问题
- "执行SQL语句时出现问题操作必须使用一个可更新的查询"错误的解决方法
- 解决了使用Firefox登录网通上网记录查询网页的问题
- Microsoft JET Database Engine 错误 '80004005' 操作必须使用一个可更新的查询。问题解决办法
- 解决模糊查询的结果记录排序问题
- 执行SQL语句时出现问题操作必须使用一个可更新的查询错误的解决方法
- ASP.Net下如何解决关于Access数据库“操作必须使用一个可更新的查询”问题
- 解决Hibernate原生SQL映射问题 - SQL查询出来的结果映射为值对象
- 在此上下文中不允许使用子查询。只允许使用标量表达式。Insert 查询结果-解决方法
- 如何解决ADO.NET访问Access数据库出现"操作必须使用一个可更新的查询"的问题(非原创,摘自书中)
- 在此上下文中不允许使用子查询。只允许使用标量表达式。Insert 查询结果-解决方法
- ASP“操作必须使用一个可更新的查询”问题的解决办法
- 不显示查询问题的解决(太完美了,新建一个图层,表示查询结果)
- 解决Access数据库中"操作必须使用一个可更新的查询"问题
- 利用冗余字段解决查询结果排序慢的问题
- 多个表联合查询的一些问题解决,用一个表里面的查询结果来排除另外一个表的查询结果