RedisTemplate混用带来的序列化问题

news2024/11/13 9:51:57

最近在工作中发现一个现象,项目中使用了不同的 RedisTemplate 来操作redis,有的同事用默认的 RedisTemplate ,有的同事用 StringRedisTemplate。这就导致了我本次遇到的问题:

在一次需求中,我需要从 redis 中取值,并且这个值是之前就有的,而我要加代码的那个类里也早早存在了 RedisTemplate 的引用

@Autowired
RedisTemplate redisTemplate;

于是直接用这个类里的 RedisTemplate 去获取 key,结果取到了 null,而翻一翻 redis,这个key 也确实是存在的,但是死活获取不到。翻看这个key 往 redis 中放值的逻辑,发现是使用的 StringRedisTemplate

@Autowired
StringRedisTemplate stringRedisTemplate;

难道是这两个 RedisTemplate 的原因?搜查了一下后发现果然是这样,这是因为两个 RedisTemplate 使用了不同的序列化方式造成的。之前一直没关注过 RedisTemplate 的序列化方式,借着本次机会也重新了解一下。

先来看下常见的自定义配置RedisTemplate的方式

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.StringRedisSerializer;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        
        // 设置连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        
        // 使用String序列化器来序列化和反序列化redis的key值
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用Jackson序列化为JSON)
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);

        // 开启事务支持
        redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

可以看到,在配置方法里需要对四个属性设置序列化与反序列化的方式,分别是 Key 与 HashKey,Value 与 HashValue。

在RedisTemplate的源码中也能看到,设置这四种值的序列化方式即可完成对 RedisTemplate 的序列化配置

看到这你可能会有疑问,redis支持五种基本类型 String、List、Set、Hash、Zset,为什么只设置了两种值的序列化配置呢?

其实  String、List、Set、Zset 序列化方式统一被 keySerializer 与 valueSerializer 两个属性设置了,这几种数据类型都是 key、value形式。而 Redis 的 Hash 数据结构的特性与其他数据结构有所不同。Hash 数据结构存储的是键值对集合,每个 Hash相当于一个小型的 key-value 存储,因此它的 hashKey 和 hashValue 序列化方式要单独配置。

什么是序列化?

序列化(Serialization)是将对象的状态转换为可以存储或传输的格式的过程。通过序列化,一个复杂的对象可以被转换成字节序列或字符串,然后存储到文件、数据库,或者通过网络传输到另一个系统。相应的,反序列化(Deserialization)是将存储或传输的字节序列转换回原来的对象的过程。

所以序列化与反序列化方式必须是配对使用的,A序列化方式 序列化的数据必须由 A反序列化方式来正确转化,B反序列化方式 极大可能是不能将数据正常转化回来的。

现在我们已经知道了 RedisTemplate 需要配置四个序列化相关的属性值。那么默认的 RedisTemplate 与 StringRedisTemplate(RedisTemplate的子类)是怎么配置这四个属性值的呢?

先看看 RedisTemplate 的部分源码

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {

    private boolean initialized = false;
    private @Nullable RedisSerializer<?> defaultSerializer;
    private boolean enableDefaultSerializer = true;

    private @Nullable RedisSerializer keySerializer = null;
    private @Nullable RedisSerializer valueSerializer = null;
    private @Nullable RedisSerializer hashKeySerializer = null;
    private @Nullable RedisSerializer hashValueSerializer = null;

    @Override
	public void afterPropertiesSet() {

		super.afterPropertiesSet();
		boolean defaultUsed = false;

        // 使用JDK自带的序列化方式作为redis的默认序列化器
		if (defaultSerializer == null) {
			defaultSerializer = new JdkSerializationRedisSerializer(
					classLoader != null ? classLoader : this.getClass().getClassLoader());
		}
        // 默认情况下 RedisTemplate 是使用 JdkSerializationRedisSerializer 来作为所有 key value 的序列化器的
		if (enableDefaultSerializer) {
			if (keySerializer == null) {
				keySerializer = defaultSerializer;
				defaultUsed = true;
			}
			if (valueSerializer == null) {
				valueSerializer = defaultSerializer;
				defaultUsed = true;
			}
			if (hashKeySerializer == null) {
				hashKeySerializer = defaultSerializer;
				defaultUsed = true;
			}
			if (hashValueSerializer == null) {
				hashValueSerializer = defaultSerializer;
				defaultUsed = true;
			}
		}

		if (enableDefaultSerializer && defaultUsed) {
			Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
		}

		if (scriptExecutor == null) {
			this.scriptExecutor = new DefaultScriptExecutor<>(this);
		}

		initialized = true;
	}

}

可以看到默认情况下 RedisTemplate 是使用 JdkSerializationRedisSerializer 来作为所有 key、value 的序列化器的,四个属性的值都是 JdkSerializationRedisSerializer;

再来看看 StringRedisTemplate 的部分源码

public class StringRedisTemplate extends RedisTemplate<String, String> {

    // 给四个属性都赋值为字符串序列化器 StringRedisSerializer
	public StringRedisTemplate() {
		setKeySerializer(RedisSerializer.string());
		setValueSerializer(RedisSerializer.string());
		setHashKeySerializer(RedisSerializer.string());
		setHashValueSerializer(RedisSerializer.string());
	}

}

RedisSerializer.string()的值为

public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);

可见 StringRedisTemplate 的所有 key、value都是使用的字符串序列化器 StringRedisSerializer。

综上所述,RedisTemplate 与 StringRedisTemplate 使用的是完全不同的两种序列化方式,理论上他们存入 redis 的内容是不能被交叉读取的,即 RedisTemplate 存的 key,StringRedisTemplate 读不到;StringRedisTemplate存的 key,RedisTemplate读不到。

这里来做个实验验证一下

@Autowired
RedisTemplate redisTemplate;

@Autowired
StringRedisTemplate stringRedisTemplate;

@Test
public void testRedisTemplate01() {

    String keyA = "r_set_aaa";
    User valueA = new User("张三",18);
    redisTemplate.opsForValue().set(keyA,valueA);

    Object value01 = redisTemplate.opsForValue().get(keyA);
    log.info("redisTemplate获取到值:{}",value01);
    User user = (User) value01;
    log.info("user.getName():{}, user.getAge():{}", user.getName(),user.getAge());

    String value02 = stringRedisTemplate.opsForValue().get(keyA);
    log.info("stringRedisTemplate获取到值:{}",value02);

}

@Data
@NoArgsConstructor
@AllArgsConstructor
// 注意要使用jdk的序列化方式的话,需要实现 Serializable 接口
private static class User implements Serializable {
    private String name;
    private Integer age;
}

运行结果

redisTemplate获取到值:RedisTemplateTest.User(name=张三, age=18)
user.getName():张三, user.getAge():18
stringRedisTemplate获取到值:null

redis中存放的键值如下

可以看到默认的 RedisTemplate 往 redis 中存入的 key 和 value 的可读性很差,redis客户端可以看到有很多乱码,但是仍可以看到其 value 值中带有对象信息;RedisTemplate 从 redis 中取出来的值直接就是一个对象,可以强转为指定对象。

这种情况下 StringRedisTemplate 就无法从 redis 中正常取出值,因为 StringRedisTemplate 在 redis 寻找的 key 是 "r_set_aaa" 这个纯净的字符串,但是显然 RedisTemplate 往 redis 中存入的 key 并不是那么纯净,所以 StringRedisTemplate 压根找不到它想要找的 key。

再来个例子,这次让 StringRedisTemplate 来往 redis 中 set 值

@Test
public void testRedisTemplate02() {
    String keyB = "sr_set_aaa";
    User valueB = new User("李四",20);
    stringRedisTemplate.opsForValue().set(keyB,JSON.toJSONString(valueB));

    String value03 = stringRedisTemplate.opsForValue().get(keyB);
    log.info("stringRedisTemplate获取到值:{}",value03);

    Object value04 = redisTemplate.opsForValue().get(keyB);
    log.info("redisTemplate获取到值:{}",value04);
}

运行结果:

stringRedisTemplate获取到值:{"age":20,"name":"李四"}
redisTemplate获取到值:null

redis中存放的键值如下

可以看到这种情况下,redis中信息的可读性要好了不少,StringRedisTemplate 往 redis 中存放的 key就是纯净的字符串,value就是我们程序中提前转化好的“User对象的 json 串”这个字符串,StringRedisTemplate 从 redis 中取值自然没问题,正常拿到了字符串; 而 RedisTemplate 就无法从 redis 中正常取出值,通过上一个例子可以知道: RedisTemplate 要找的 key 不是程序中的那个简单的字符串,而是附加了其他的信息的(乱码的前缀信息), 所以RedisTemplate 自然也就找不到指定的 key。

所以说,一个项目中的公共组件,大家最好提前定义好,都用同一个,否则的话 五花八门的用法极易出现问题,且程序扩展性很差。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AIGC产品经理自学不迷路!全网最全AI产品大模型的学习教程!

一、《AI产品经理知识扫盲》 1️⃣【AI的产品形态到底是什么】&#xff1f; 没有固定形态。实际上AI只是一种对传统产品或服务赋能的手段而已&#xff0c;将各种“中间件”&#xff08;通常是一种训练好的模型&#xff0c;当输入一定数据后自动返回一定的输出值&#xff09;、…

【WRF工具】WRF Domain Wizard第一期:软件下载及安装

【WRF工具介绍】WRF Domain Wizard下载及安装 1 WRF Domain Wizard 的主要功能2 使用 WRF Domain Wizard 的步骤2.1 安装 WRF Domain Wizard&#xff1a;2.2 启动 WRF Domain Wizard&#xff1a;2.3 定义计算域&#xff1a;2.4 生成配置文件&#xff1a;2.5 运行 WPS 和 WRF&am…

内核是如何接收网络包的

1、数据如何从网卡到网络协议栈 1.1内核收包的过程 1、数据帧从外部网络到达网卡 2、网卡把数据帧从自己的缓存DMA(拷贝到)和内核共有的RingBuffer上 3、网卡发出硬中断通知CPU 4、CPU响应硬中断&#xff0c;简单处理后发出软中断 5、k’softirqd线程处理软中断&#xff0c;调…

状态估计算法

目录 前言一、贝叶斯滤波二、卡尔曼滤波2.1 KF简介2.2 基本线性模型2.3 KF公式推导2.3.1 预测值2.3.2 先验误差协方差矩阵2.3.3 卡尔曼增益2.3.4 最优估计值2.3.5 后验误差协方差矩阵 2.4 KF算法使用2.5 MATLAB验证2.5 Python验证 三、扩展卡尔曼滤波3.1 EKF原理3.2 MATLAB实现…

基于vue框架的宠物寻回小程序8g7el(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;发布人,宠物分类,宠物信息,接取人,接取信息,完成信息 开题报告内容 基于Vue框架的宠物寻回小程序开题报告 一、研究背景与意义 随着城市化进程的加快和人们生活水平的提高&#xff0c;宠物已成为许多家庭不可或缺的一员。它们不仅为生…

谷歌导入了自我填充密码,不显示

C:\Users\GZDZ\AppData\Local\Google\Chrome\User Data\Default Login Data Login Data-journal 删除上面两个重启就可以了 https://blog.csdn.net/weixin_30940783/article/details/97552679?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandi…

Windows10电脑右下角时间显示到秒

1、打开注册表 快捷键 WIN R 键&#xff0c;输入 regedit 在注册表中找到如下位置 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced 在空白位置右击&#xff0c;新建--DWORD(32位)值(D) 3、将新建的数值名称设置为 ShowSecondsInSystemCloc…

AI绘画与摄影新纪元:ChatGPT+Midjourney+文心一格 共绘梦幻世界

文章目录 一、AI艺术的新时代二、ChatGPT&#xff1a;创意的引擎与灵感的火花三、Midjourney&#xff1a;图像生成的魔法与技术的奇迹四、文心一格&#xff1a;艺术的升华与情感的共鸣五、融合创新&#xff1a;AI绘画与摄影实战的无限可能六、应用场景与实践案例AI艺术的美好未…

【Delphi】知道控件名称(字符串),访问控件

在 Delphi 中&#xff0c;可以使用 RTTI&#xff08;运行时类型信息&#xff09; 或其他方法通过对象的名称字符串来访问对象。比如&#xff0c;如果你有一个控件的名称字符串&#xff0c;你希望通过该名称找到并访问实际的控件。 以下是通过 RTTI 以及其他技术&#xff08;如…

react之jsx基础(1)概念和本质

文章目录 JSX 的基本概念1. **语法**2. **表达式**3. **属性**4. **子元素** JSX 的编译过程1. **转换成 JavaScript**2. **React 元素** JSX 的实际应用1. **组件定义**2. **组件嵌套** 总结 当然&#xff0c;以下是对 JSX 的详细讲解&#xff0c;包括其基本概念、语法、编译过…

【vue element-ui】关于删除按钮的提示框,可一键复制

实现效果&#xff1a; Delete: function (id) {this.$confirm(此操作将永久删除该文件, 是否继续?, 提示, {confirmButtonText: 确定,cancelButtonText: 取消,type: warning,center: true,}).then(() > {Delete(id).then(() > {this.$message({type: success,message: 删…

基于python+django+vue的图书管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的图…

Modbus_tcp

目录 一&#xff1a;modbus起源 1.起源 2. 分类&#xff1a; 3. 优势&#xff1a; 4. 应用场景&#xff1a; 5.ModbusTCP特点&#xff08;掌握&#xff09;&#xff1a; 二、 ModbusTCP的协议 1. 报文头 2. 寄存器 1. 线圈&#xff08;Coils&#xff09; 2. 离…

数据库———事务及bug的解决

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能帮到你&#xff01; 目录 一&#xff1a;事务 1&#xff1a;场景引入 2&#xff1a;“回滚” 3&#xff1a;恢复机制&#xff08;un…

全网最全comfyui工作流保姆级教程来啦!comfyui工作流搭建看这一篇就够了

前言 一、SD主流 UI Stable Diffusion&#xff08;SD&#xff09;因为其开源特性&#xff0c;有着较高的受欢迎程度&#xff0c;并且基于SD的开源社区及教程、插件等&#xff0c;都是所有工具里最多的。基于SD&#xff0c;有不同的操作界面&#xff0c;可以理解为一个工具的不…

vue node node-sass sass-loader 版本 对应 与 兼容

警告&#xff1a; LibSass 和 Node Sass 已弃用。虽然它们将继续无限期地接收维护版本&#xff0c;但没有计划添加其他功能或与任何新的 CSS 或 Sass 功能兼容。仍在使用它的项目应该转移到 Dart Sass。 sass Sass是一种预处理器脚本语言&#xff0c;可以解释或编译成…

【rust】rust条件编译

在c语言中&#xff0c;条件编译是一个非常好用的功能&#xff0c;那么rust中如何实现条件编译呢? rust的条件编译需要两个部分&#xff0c;一个是fratures&#xff0c;另一个是cfg。Cargo feature是一个非常强大的功能&#xff0c;可以提供条件编译和可选依赖项的高级特性&…

如何让大模型更好地进行场景落地?

自ChatGPT模型问世后&#xff0c;在全球范围内掀起了AI新浪潮。 有很多企业和高校也随之开源了一些效果优异的大模型&#xff0c;例如&#xff1a;Qwen系列模型、MiniCPM序列模型、Yi系列模型、ChatGLM系列模型、Llama系列模型、Baichuan系列模型、Deepseek系列模型、Moss模型…

OJ 组合总和

题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制…

en造数据结构与算法C# 用Unity实现简单的群组行为算法 之 分散

因为还没写聚集并且材质没有设置摩擦系数&#xff0c;所以出现了这种刚体受力得到初速度却无法减少&#xff0c;从而乱飞的情况 本教程部分代码师承于siki学院siki老师的人工智能编程这一案例&#xff0c;我认为自己的水平有限&#xff0c;老师的写法太过高级&#xff0c;所以就…