Redis之RedisTemplate的序列化方式深入解读

Posted 头顶假发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis之RedisTemplate的序列化方式深入解读相关的知识,希望对你有一定的参考价值。

概述

使用Spring提供的Spring Data Redis操作redis必然要使用Spring提供的模板类RedisTemplate,使用RedisTemplate离不开Redis的序列化方式,今天通过本篇文章主要讲解Redis序列化的那些坑。

RedisTemplate

可以看到4个序列化相关的属性 ,主要是用于KEY和VALUE的序列化,比如说我们经常会将POJO对象存储到Redis中,一般情况下会使用JSON方式序列化成字符串存储到Redis中 。

Spring提供的Redis数据结构的操作类

  • ValueOperations 类,提供 Redis String API 操作
  • ListOperations 类,提供 Redis List API 操作
  • SetOperations 类,提供 Redis Set API 操作
  • ZSetOperations 类,提供 Redis ZSet(Sorted Set) API 操作
  • GeoOperations 类,提供 Redis Geo API 操作
  • HyperLogLogOperations 类,提供 Redis HyperLogLog API 操作

StringRedisTemplate

RedisTemplate支持泛型,StringRedisTemplate K/V 均为String类型。


org.springframework.data.redis.core.StringRedisTemplate 继承RedisTemplate类,使用
org.springframework.data.redis.serializer.StringRedisSerializer字符串序列化方式。

RedisSerializer序列化接口

RedisSerializer接口是Redis序列化接口,用于Redis KEY和VALUE的序列化。

RedisSerializer接口的实现类如下:

默认Redis提供了11中的序列化方式,归类一下主要分为:

  • JDK序列化方式(默认)
  • String序列化方式
  • JSON序列化方式
  • XML序列化方式

JDK序列化方式(默认)


org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,默认不配置的情况RedisTemplate采用的是该数据序列化方式,可以查看一下源码:

Spring Boot自动化配置RedisTemplate Bean对象时,就未设置默认的序列化方式。绝大多数情况下,并不推荐使用
JdkSerializationRedisSerializer进行序列化。主要是不方便人工排查数据。我们来做个测试: 

运行单元测试:

发现key跟value的值都是16进制字符串,可以看到key跟value实际上保存的都是以byte[]字节数组的格式存储:

key被序列化成这样,线上通过key去查询对应的value非常不方便,所以key肯定是不能被这样序列化的。value被序列化成这样,除了阅读可能困难一点,不支持跨语言外,实际上也没多大问题。不过,实际线上场景,还是使用JSON序列化居多。

String序列化方式


org.springframework.data.redis.serializer.StringRedisSerializer,字符串和二进制数组都直接转换:

默认的话,StringRedisTemplate的key和value采用的就是这种序列化方案。

JSON序列话方式

GenericJackson2JsonRedisSerializer


org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer 使用Jackson 实现JSON的序列化方式,Generic单词翻译过来表示:通用的意思,可以看出,是支持所有类。

RedisConfig配置

通过配置方式选择对应Redis数据的序列化方式,配置如下:

package com.example.jedisserializefrombytestojson.config;

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置
 *
 * @author: jacklin
 * @date: 2022/9/9 0:07
 */
@Configuration
public class RedisConfig 

    //GenericJackson2JsonRedisSerializer
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //String的序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // 使用GenericJackson2JsonRedisSerializer 替换默认序列化(默认采用的是JDK序列化)
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        //key序列化方式采用String类型
        template.setKeySerializer(stringRedisSerializer);
        //value序列化方式采用jackson类型
        template.setValueSerializer(genericJackson2JsonRedisSerializer);
        //hash的key序列化方式也是采用String类型
        template.setHashKeySerializer(stringRedisSerializer);
        //hash的value也是采用jackson类型
        template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    

    Jackson2JsonRedisSerializer
    //@Bean
    //@ConditionalOnMissingBean(name = "redisTemplate")
    //public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) 
    //    RedisTemplate<String, Object> template = new RedisTemplate<>();
    //    template.setConnectionFactory(factory);
    //
    //    //String的序列化方式
    //    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    //    // 使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化)
    //    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    //
    //    //key序列化方式采用String类型
    //    template.setKeySerializer(stringRedisSerializer);
    //    //value序列化方式采用jackson类型
    //    template.setValueSerializer(jackson2JsonRedisSerializer);
    //    //hash的key序列化方式也是采用String类型
    //    template.setHashKeySerializer(stringRedisSerializer);
    //    //hash的value也是采用jackson类型
    //    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    //    template.afterPropertiesSet();
    //    return template;
    //
    //
    FastJsonRedisSerializer
    //@Bean("redisTemplate")
    //public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
    //    RedisTemplate<String, Object> template = new RedisTemplate<>();
    //    template.setConnectionFactory(factory);
    //
    //    //String序列化方式
    //    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    //    // 使用FastJsonRedisSerializer替换默认序列化(默认采用的是JDK序列化)
    //    FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
    //
    //    //key序列化方式采用String类型
    //    template.setKeySerializer(stringRedisSerializer);
    //    //value序列化方式采用jackson类型
    //    template.setValueSerializer(fastJsonRedisSerializer);
    //    //hash的key序列化方式也是采用String类型
    //    template.setHashKeySerializer(stringRedisSerializer);
    //    //hash的value也是采用jackson类型
    //    template.setHashValueSerializer(fastJsonRedisSerializer);
    //    template.afterPropertiesSet();
    //    return template;
    //


复制代码

运行以下测试类:

@Test
void redisTemplateSerializeTest() 
    String redisTemplateStringKey = "redisTemplateStringKey";
    String redisTemplateUserObjectKey = "redisTemplateUserObjectKey";
    String redisTemplateUserArrayObjectKey = "redisTemplateUserArrayObjectKey";
    String redisTemplateJSONObjectKey = "redisTemplateJSONObjectKey";
    String redisTemplateJSONArrayKey = "redisTemplateJSONArrayKey";

    //序列化String类型和反序列化String类型
    redisTemplate.opsForValue().set(redisTemplateStringKey, "austin");
    String austin = (String) redisTemplate.opsForValue().get(redisTemplateStringKey);
    System.out.println("stringGet: " + austin);

    //序列化Object对象类型和反序列化Object对象类型 (User对象)
    User user = new User("123", "austin", 25);
    redisTemplate.opsForValue().set(redisTemplateUserObjectKey, user);
    User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
    System.out.println("userGet: " + userGet);

    //序列化Object对象数组类型和反序列化Object对象数组类型 (User[]对象数组)
    User user1 = new User("1", "austin1", 25);
    User user2 = new User("2", "austin2", 25);
    User[] userArray = new User[]user1, user2;
    redisTemplate.opsForValue().set(redisTemplateUserArrayObjectKey, userArray);
    User[] userArrayGet = (User[]) redisTemplate.opsForValue().get(redisTemplateUserArrayObjectKey);
    System.out.println("userArrayGet: " + userArrayGet);

    //序列化JSONObject对象类型和反序列化JSONObject对象类型
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("id", "123");
    jsonObject.put("name", "austin");
    jsonObject.put("age", 25);
    redisTemplate.opsForValue().set(redisTemplateJSONObjectKey, jsonObject);
    JSONObject jsonObjectGet = (JSONObject) redisTemplate.opsForValue().get(redisTemplateJSONObjectKey);
    System.out.println("jsonObjectGet: " + jsonObjectGet);

    //序列化JSONArray对象类型和反序列化JSONArray对象类型
    JSONArray jsonArray = new JSONArray();
    JSONObject jsonObject1 = new JSONObject();
    jsonObject1.put("id", "1");
    jsonObject1.put("name", "austin1");
    jsonObject1.put("age", 25);
    JSONObject jsonObject2 = new JSONObject();
    jsonObject2.put("id", "1");
    jsonObject2.put("name", "austin2");
    jsonObject2.put("age", 25);
    jsonArray.add(jsonObject1);
    jsonArray.add(jsonObject2);
    redisTemplate.opsForValue().set(redisTemplateJSONArrayKey, jsonArray);
    JSONArray jsonArrayGet = (JSONArray) redisTemplate.opsForValue().get(redisTemplateJSONArrayKey);
    System.out.println("jsonArrayGet: " + jsonArrayGet);

复制代码

观察redis数据的存储格式:

key- value :

  • 字符串类型
Key: redisTemplateStringKey
Value: "austin"
复制代码
  • 对象类型
Key: redisTemplateUserObjectKey
Value:

    "@class": "com.example.jedisserializefrombytestojson.User",
    "id": "123",
    "name": "austin",
    "age": 25


复制代码
  • 对象数组类型
Key: redisTemplateUserArrayObjectKey
Value: 
[
    "[Lcom.example.jedisserializefrombytestojson.User;",
    [
        
            "@class": "com.example.jedisserializefrombytestojson.User",
            "id": "1",
            "name": "austin1",
            "age": 25
        ,
        
            "@class": "com.example.jedisserializefrombytestojson.User",
            "id": "2",
            "name": "austin2",
            "age": 25
        
    ]
]
复制代码
  • JSONObject类型
Key: redisTemplateJSONObjectKey
Value:

    "@class": "com.alibaba.fastjson.JSONObject",
    "name": "austin",
    "id": "123",
    "age": 25

复制代码
  • JSONArray类型
Key: redisTemplateJSONArrayKey
Value: 
[
    "com.alibaba.fastjson.JSONArray",
    [
        
            "@class": "com.alibaba.fastjson.JSONObject",
            "name": "austin1",
            "id": "1",
            "age": 25
        ,
        
            "@class": "com.alibaba.fastjson.JSONObject",
            "name": "austin2",
            "id": "1",
            "age": 25
        
    ]
]

复制代码

运行
redisTemplateSerializeTest测试类,结果发现该方式序列化和反序列化都没有问题,果然是通用性序列化方式:

我们来思考下,在将一个对象序列化成一个字符串,怎么保证字符串反序列化成对象的类型呢?Jackson通过 Default Typing,会在字符串多冗余一个类型,这样反序列化就知道具体的类型了。

从结果发现,使用
GenericJackson2JsonRedisSerializer序列化方式,String类型、对象、对象数组、JSONObject、JSONArray序列化和反序列化都没有问题,value值序列化后多了@class属性,反序列化的对象的类型就可以从这里获取到。@class属性完美解决了反序列化后的对象类型,所以实际项目中,目前很多采用 GenericJackson2JsonRedisSerializer序列化方式。

Jackson2JsonRedisSerializer

org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer

观察redis数据的存储格式:

key- value:

  • 字符串类型
Key: redisTemplateStringKey
Value: "austin"
复制代码
  • 对象类型
Key: redisTemplateUserObjectKey
Value:

    "id": "123",
    "name": "austin",
    "age": 25


复制代码

与上面
GenericJackson2JsonRedisSerializer序列化方式结果不同的是,value没有@class属性。

  • 对象数组类型
Key: redisTemplateUserArrayObjectKey
Value: 
[
    
        "id": "1",
        "name": "austin1",
        "age": 25
    ,
    
        "id": "2",
        "name": "austin2",
        "age": 25
    
]
复制代码

与上面
GenericJackson2JsonRedisSerializer序列化方式结果不同的是,value没有"@class": "com.example.jedisserializefrombytestojson.User" 对象类型属性。

  • JSONObject类型
Key: redisTemplateJSONObjectKey
Value:

    "name": "austin",
    "id": "123",
    "age": 25

复制代码

与上面
GenericJackson2JsonRedisSerializer序列化方式结果不同的是,value没有"@class": "com.alibaba.fastjson.JSONObject"属性。

  • JSONArray类型
Key: redisTemplateJSONArrayKey
Value: 
[
    
        "name": "austin1",
        "id": "1",
        "age": 25
    ,
    
        "name": "austin2",
        "id": "1",
        "age": 25
    
]
复制代码

与上面
GenericJackson2JsonRedisSerializer序列化方式结果不同的是,value没有 "com.alibaba.fastjson.JSONArray" 对象类型属性。


Jackson2JsonRedisSerializer与GenericJackson2JsonRedisSerializer序列化结果不同的是,前者并没有@class或者@type类型属性,这种序列化方式可能会导致获取redis数据反序列化成POJO对象时候出错,导致反序列化失败,所以一般也很少使用该方式。

比如在对象强制转换的情况,会报错:

报错信息很明显,不能直接将JSONObject对象强制转换成User对象,不能通过方式获取转换:

//该方式强转会报错
User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
复制代码

而正确的方式应该是:

//通过com.fastxml.jackson的ObjectMapper对象进行转换
Object userObject = redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
ObjectMapper objectMapper = new ObjectMapper();
User userGet = objectMapper.convertValue(userObject, User.class);
System.out.println("userGet: " + userGet);
复制代码

 

这也是redis序列化和反序列化主要非常注意地方。

总结

采用
GenericJackson2JsonRedisSerializer序列化方式对于String、对象、对象数组、JSONObject、JSONArray的序列化反序列化操作都正常,对象强转是没有任何问题,但是采用
Jackson2JsonRedisSerializer序列化方式在对象强制时,也就是使用 User userGet = (User) redisTemplate.opsForValue().get(
redisTemplateUserObjectKey);方式获取对象,会操作对象转换失败,建议的解决方式是默认都采用 com.fastxml.jackson的ObjectMapper对象进行转换,也就是:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.convertValue(Object fromValue, Class<T> toValueType);
复制代码

该方式支持将任意类型的Object对象转换为相应的实体对象。

RedisTemplate 序列化问题

spring-data-redis RedisTemplate 操作redis时发现存储在redis中的key不是设置的string值,前面还多出了许多类似xacxedx00x05tx00;

因为spring-data-redis的RedisTemplate<K, V>模板类在操作redis时默认使用JdkSerializationRedisSerializer来进行序列化,出现错误键不一致的问题。

可以配置序列化:

RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);

 

以上是关于Redis之RedisTemplate的序列化方式深入解读的主要内容,如果未能解决你的问题,请参考以下文章

RedisTemplate 序列化问题

Spring Data Redis 序列化

Redis 数据类型操作指令以及对应的RedisTemplate方法

213-RedisTemplate如何实现操作Redis?

213-RedisTemplate如何实现操作Redis?

Spring Data Redis入门示例:基于RedisTemplate