Redis之RedisTemplate配置方式(序列和反序列化)


Posted in Redis onMarch 13, 2022

RedisTemplate配置序列和反序列化

对于redis操作,springboot进行了很好的封装,那就是spring data redis。提供了一个高度封装的RedisTemplate类来进行一系列redis操作,连接池自动管理;同时将事务封装操作,交由容器进行处理。

针对数据的“序列化和反序列化”,提供了多种策略(RedisSerializer)

默认为使用JdkSerializationRedisSerializer,同时还有StringRedisSerializer,JacksonJsonRedisSerializer,OxmSerializer,GenericFastJsonRedisSerializer。

简介一下

  • JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前默认的序列化策略。
  • StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
  • JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】
  • GenericFastJsonRedisSerializer:另一种javabean与json之间的转换,同时也需要指定Class类型。
  • OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】

实践

1)依赖(版本继承了SpringBoot版本)

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2)RedisConfig类

添加bean,指定key/value以及HashKey和HashValue的序列化和反序列化为FastJson的。

package com.sleb.springcloud.common.config;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
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.serializer.GenericToStringSerializer;
/**
 * redis配置
 * @author 追到乌云的尽头找太阳(Jacob)
 **/
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用 GenericFastJsonRedisSerializer 替换默认序列化
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        // 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new GenericToStringSerializer<>(Object.class));
        redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);
        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new GenericToStringSerializer<>(Object.class));
        redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);
        // 设置支持事物
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

RedisTemplate序列化问题

序列化与反序列化规则不一致,导致报错

1、配置redisTemplate

<!-- redis数据源 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空闲数 -->
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <!-- 最大空连接数 -->
        <property name="maxTotal" value="${redis.maxTotal}"/>
        <!-- 最大等待时间 -->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        <!-- 返回连接时,检测连接是否成功 -->
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>
<!-- Spring-data-redis连接池管理工厂 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- IP地址 -->
        <property name="hostName" value="${redis.host}"/>
        <!-- 端口号 -->
        <property name="port" value="${redis.port}"/>
        <!-- 密码 -->
<!--        <property name="password" value="${redis.password}"/>-->
        <!-- 超时时间 默认2000 -->
        <property name="timeout" value="${redis.timeout}"/>
        <!-- 连接池配置引用 -->
        <property name="poolConfig" ref="poolConfig"/>
        <!-- 是否使用连接池 -->
        <property name="usePool" value="true"/>
        <!-- 指定使用的数据库 -->
        <property name="database" value="0"/>
    </bean>
<!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
    </bean>

2、存值

此次存值,使用redisTemplate的回调函数,是按照字符串序列化方式存redisValue

    public void testRedisListPush() {
        String redisKey = "testGoodsKey";
        List<String> redisValues = Arrays.asList("10002001", "10002002");
        // 使用管道向redis list结构中批量插入元素
        redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            // 打开管道
            redisConnection.openPipeline();
            // 给本次管道内添加,一次性执行的多条命令
            for (String redisValue : redisValues) {
                redisConnection.rPush(redisKey.getBytes(), redisValue.getBytes());
            }
            return null;
        });
    }

redis客户端:value是字符串

Redis之RedisTemplate配置方式(序列和反序列化)

3、取值

此次取值,返回结果默认是按照 1、配置redisTemplate中配置的JdkSerializationRedisSerializer序列化方式,由于存和取的序列化方式不统一,会产生报错情况。

public void testRedisListPop() {
        String redisKey = "testGoodsKey";
        // 使用管道从redis list结构中批量获取元素
        List<Object> objects = redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            // 打开管道
            redisConnection.openPipeline();
            for (int i = 0; i < 2; i++) {
                redisConnection.rPop(redisKey.getBytes());
            }
            return null;
        });
        System.out.println(objects);
    }

报错详情:反序列化失败


org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.StreamCorruptedException: invalid stream header: 31303030
...
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.StreamCorruptedException: invalid stream header: 31303030
    at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:78)
    at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:36)
    at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:80)
    ... 39 more
Caused by: java.io.StreamCorruptedException: invalid stream header: 31303030
    at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:899)
    at java.io.ObjectInputStream.<init>(ObjectInputStream.java:357)
    at org.springframework.core.ConfigurableObjectInputStream.<init>(ConfigurableObjectInputStream.java:63)
    at org.springframework.core.ConfigurableObjectInputStream.<init>(ConfigurableObjectInputStream.java:49)
    at org.springframework.core.serializer.DefaultDeserializer.deserialize(DefaultDeserializer.java:68)
    at org.springframework.core.serializer.support.DeserializingConverter.convert(DeserializingConverter.java:73)
    ... 41 more

解决办法

1、取值

需要在redisTemplate.executePipelined入参中再加一个参数:redisTemplate.getStringSerializer(),取值成功,解决问题!!

    public void testRedisListPop() {
        String redisKey = "testGoodsKey";
        // 使用管道从redis list结构中批量获取元素
        List<Object> objects = redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            // 打开管道
            redisConnection.openPipeline();
            for (int i = 0; i < 2; i++) {
                redisConnection.rPop(redisKey.getBytes());
            }
            return null;
        }, redisTemplate.getStringSerializer());
        System.out.println(objects);
    }

总结

1、使用原生redisTemplate操作数据和redisTemplate回调函数操作数据注意点:

a.原生redisTemplate操作数据

代码

    public void testRedisListPush() {
        String redisKey = "testGoodsKey";
        List<String> redisValues = Arrays.asList("10002001", "10002002");
        redisValues.forEach(redisValue -> redisTemplate.opsForList().rightPush(redisKey, redisValue));
    }

redis客户端数据展示

Redis之RedisTemplate配置方式(序列和反序列化)

b.redisTemplate回调函数操作数据

代码

    public void testRedisListPush() {
        String redisKey = "testGoodsKey";
        List<String> redisValues = Arrays.asList("10002001", "10002002");
        // 使用管道向redis list结构中批量插入元素
        redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            // 打开管道
            redisConnection.openPipeline();
            // 给本次管道内添加,一次性执行的多条命令
            for (String redisValue : redisValues) {
                redisConnection.rPush(redisKey.getBytes(), redisValue.getBytes());
            }
            return null;
        });
    }

redis客户端数据展示

Redis之RedisTemplate配置方式(序列和反序列化)

c.不同点:

原生redisTemplate操作数据序列化方式是和redis配置统一的,redisTemplate回调函数操作数据序列化方式是自定义的。存值取值是需要注意。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Redis 相关文章推荐
redis三种高可用方式部署的实现
May 11 Redis
详解Redis瘦身指南
May 26 Redis
浅谈Redis主从复制以及主从复制原理
May 29 Redis
SpringBoot 集成Redis 过程
Jun 02 Redis
Redis入门教程详解
Aug 30 Redis
redis缓存存储Session原理机制
Nov 20 Redis
解决redis批量删除key值的问题
Mar 23 Redis
Redis 哨兵机制及配置实现
Mar 25 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
Redis实现短信验证码登录的示例代码
Jun 14 Redis
Redis Lua脚本实现ip限流示例
Jul 15 Redis
浅谈Redis跟MySQL的双写问题解决方案
解决linux下redis数据库overcommit_memory问题
Feb 24 #Redis
解决Redis启动警告问题
分布式Redis Cluster集群搭建与Redis基本用法
Redis命令处理过程源码解析
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
You might like
3种平台下安装php4经验点滴
2006/10/09 PHP
set_include_path和get_include_path使用及注意事项
2013/02/02 PHP
浅谈php7的重大新特性
2015/10/23 PHP
JavaScript获得选中文本内容的方法
2008/12/02 Javascript
用显卡加速,轻松把笔记本打造成取暖器的办法!
2013/04/17 Javascript
setTimeout和setInterval的深入理解
2013/11/08 Javascript
javascript实现在下拉列表中显示多级树形菜单的方法
2015/08/12 Javascript
js实现弹窗居中的简单实例
2016/10/09 Javascript
bootstrap table小案例
2016/10/21 Javascript
关于使用axios的一些心得技巧分享
2017/07/02 Javascript
使用js获取伪元素的content实例
2017/10/24 Javascript
vue中添加mp3音频文件的方法
2018/03/02 Javascript
详解js类型判断
2018/05/22 Javascript
基于rollup的组件库打包体积优化小结
2018/06/18 Javascript
详解JavaScript对数组操作(添加/删除/截取/排序/倒序)
2019/04/28 Javascript
基于vue、react实现倒计时效果
2019/08/26 Javascript
详解vue-router 动态路由下子页面多页共活的解决方案
2019/12/22 Javascript
Chrome插件开发系列一:弹窗终结者开发实战
2020/10/02 Javascript
通过JS判断网页是否为手机打开
2020/10/28 Javascript
Python中的对象,方法,类,实例,函数用法分析
2015/01/15 Python
Python编码类型转换方法详解
2016/07/01 Python
详解Python的Lambda函数与排序
2016/10/25 Python
python中解析json格式文件的方法示例
2017/05/03 Python
pycharm 2020 1.1的安装流程
2020/09/29 Python
Html5实现文件异步上传功能
2017/05/19 HTML / CSS
amazeui时间组件的实现示例
2020/08/18 HTML / CSS
广州品高软件.net笔面试题目
2012/04/18 面试题
自我评价中英文语句
2013/11/30 职场文书
应聘医药销售自荐书范文
2014/02/08 职场文书
购房意向书
2014/04/01 职场文书
我爱家乡演讲稿
2014/09/12 职场文书
2016年优秀团支部事迹材料
2016/02/26 职场文书
go原生库的中bytes.Buffer用法
2021/04/25 Golang
Node实现搜索框进行模糊查询
2021/06/28 Javascript
阿里云服务器Ubuntu 20.04上安装Odoo 15
2022/05/20 Servers
Linux服务器离线安装 nginx的详细步骤
2022/06/16 Servers