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

原创 2015年07月08日 10:32:21

应用场景

我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力。在执行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 Data redis几种对象序列化的比较

GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化 Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSeria...
  • hotdust
  • hotdust
  • 2016年07月05日 17:09
  • 7532

Jackson2JsonRedisSerializer报错Could not read JSON: Unrecognized field...

nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized f...
  • baidu_29092471
  • baidu_29092471
  • 2017年02月15日 13:29
  • 2057

Spring Redis(3)序列化

序列化Spring Data Redis支持JDK序列化、Json序列化、XML序列化 - JDK序列化:默认采用JDK序列化方式的类JdkSerializationRedisSerializer,...
  • supermancoke
  • supermancoke
  • 2017年04月11日 14:55
  • 2298

Redis的Spring配置讲解

Redis是一种特殊类型的数据库,他被称之为key-value存储本文覆盖缓存和存储两方面进行说明,使用的是Spring 4.0和Java配置方式代码地址下载地址:https://github.com...
  • u012150370
  • u012150370
  • 2016年07月06日 09:11
  • 8521

2 Springboot中使用redis,配置redis的key value生成策略

redis在springboot中的应用,springboot整合redis,redis存储集合
  • tianyaleixiaowu
  • tianyaleixiaowu
  • 2017年04月24日 14:20
  • 8129

SpringBoot项目中使用redis缓存

1.概述在应用中有效的利用redis缓存可以很好的提升系统性能,特别是对于查询操作,可以有效的减少数据库压力。具体的代码参照该 示例项目2.添加引用在build.gradle加入compile('or...
  • haiyan_qi
  • haiyan_qi
  • 2017年12月03日 20:32
  • 117

RedisTemplate序列化

Spring提供了默认的StringSerializer和JdkSerializer。 StringSerializer就是通过String.getBytes()来实现的,而且在Redis中,所有存储...
  • Kincym
  • Kincym
  • 2017年12月05日 13:42
  • 59

RedisTemplate的使用说明(常用接口方法)

在RedisTemplate中提供了几个常用的接口方法的使用,分别是:private ValueOperations valueOps; private ListOperations listOps;...
  • whatlookingfor
  • whatlookingfor
  • 2016年07月08日 18:47
  • 24794

使用Spring Data Redis时,遇到的几个问题

需求: 1,保存一个key-value形式的结构到redis 2,把一个对象保存成hash形式的结构到redis...
  • hotdust
  • hotdust
  • 2016年08月23日 17:33
  • 7370

Spring Data+Redis缓存实现

Spring Data+Redis缓存实现
  • LittleSkey
  • LittleSkey
  • 2016年09月21日 21:33
  • 2513
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决
举报原因:
原因补充:

(最多只允许输入30个字)