使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决

Posted 星朝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决相关的知识,希望对你有一定的参考价值。

应用场景

我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力。在执行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缓存数据库查询结果中序列化问题的解决的主要内容,如果未能解决你的问题,请参考以下文章

04-使用Spring Cache+Redis来完成对字典数据缓存

Spring Boot (24) 使用Spring Cache集成Redis

Spring Boot之集成Redis:Spring Cache + Redis

重学SpringBoot系列之redis与spring cache缓存

Spring Cache:如何使用redis进行缓存数据?

spring boot redis 缓存(cache)集成