Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 364|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    昨天 22:25
  • 签到天数: 790 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722766
    发表于 2021-5-29 21:05:20 | 显示全部楼层 |阅读模式

    应用场景

    我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力。在执行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对象,问题解决。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-11 05:38 , Processed in 0.063069 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表