Spring Boot中混合使用StringRedisTemplate和RedisTemplate的坑

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Spring Boot中混合使用StringRedisTemplate和RedisTemplate的坑

在《SpringBoot视频教程全家桶》系列教程中,我们分别讲解了StringRedisTemplate和RedisTemplate的使用和区别。


但在实践中,有朋友遇到这样的问题,就是存储到Redis数据取不到值。


两种Template的源码分析

这是为什么呢?是因为他同时使用了StringRedisTemplate和RedisTemplate在Redis中存储和读取数据。它们最重要的一个区别就是默认采用的序列化方式不同(在课程中已经讲到)。这里我们再来回顾一下相关源码,StringRedisTemplate的部分源码如下:


public class StringRedisTemplate extends RedisTemplate<String, String> {
  /**
   * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
   * and {@link #afterPropertiesSet()} still need to be called.
   */
  public StringRedisTemplate() {
    setKeySerializer(RedisSerializer.string());
    setValueSerializer(RedisSerializer.string());
    setHashKeySerializer(RedisSerializer.string());
    setHashValueSerializer(RedisSerializer.string());
  }
}

通过该源码我们可以看到StringRedisTemplate采用的是RedisSerializer.string()来序列化Redis中存储数据的Key的。

下面再来看看RedisTemplate中默认采用什么形式来序列化对应的Key。

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
    // 省略其他源码
  private @Nullable RedisSerializer<?> defaultSerializer;
  private @Nullable ClassLoader classLoader;
  /*
   * (non-Javadoc)
   * @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet()
   */
  @Override
  public void afterPropertiesSet() {
    super.afterPropertiesSet();
    boolean defaultUsed = false;
    if (defaultSerializer == null) {
      defaultSerializer = new JdkSerializationRedisSerializer(
          classLoader != null ? classLoader : this.getClass().getClassLoader());
    }
    if (enableDefaultSerializer) {
      if (keySerializer == null) {
        keySerializer = 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使用的序列化类为defaultSerializer,默认情况下为JdkSerializationRedisSerializer。如果未指定Key的序列化类,keySerializer与defaultSerializer采用相同的序列化类。


通过上述两个Template的分析我们就可以看出它们在Redis存储的Key,采用了不同的序列化方法。


而且JdkSerializationRedisSerializer序列化时会在Key的前面添加一些特殊字符。


还原测试

下面先看一个单元测试:

@Slf4j
@SpringBootTest
class RedisDifferentTemplateTest {
  @Resource
  private RedisTemplate<String, Object> redisTemplate;
  @Resource
  private StringRedisTemplate stringRedisTemplate;
  @Test
  void testSimple() {
    redisTemplate.opsForValue().set("myWeb", "www.choupangxia.com");
    Assertions.assertEquals("www.choupangxia.com", redisTemplate.opsForValue().get("myWeb"));
    Assertions.assertEquals("www.choupangxia.com",stringRedisTemplate.opsForValue().get("myWeb"));
  }
}

在上述方法中先通过redisTemplate存储一个key为myWeb的数据到Redis中,随后通过redisTemplate获取并判断断言,可以成功通过。但随后通过stringRedisTemplate获取同样的key的值,则抛出异常,异常信息如下:

org.opentest4j.AssertionFailedError: 
Expected :www.choupangxia.com
Actual   :null
 <Click to see difference>

也就是说获取的结果为null。

那么,我们再通过Redis客户端看一下两种形式存储到redis中key的值的情况。

image.png我们可以看到通过StringRedisTemplate存储的数据Key为“myWeb”,而RedisTemplate存储的Key为“\xAC\xED\x00\x05t\x00\x05myWeb”,这也就是为什么默认情况下两者存储的数据没办法混合使用了。


解决方案

那么,如果在生产环境中想通用StringRedisTemplate和RedisTemplate进行字符串的处理该怎么办?


此时就需要指定统一的Key的序列化处理类,比如在RedisTemplate序列化时指定与StringRedisTemplate相同的类。


在上述单元测试中添加如下方法:


@BeforeEach

void init() {

redisTemplate.setKeySerializer(RedisSerializer.string());

}


也就是设置RedisTemplate也使用RedisSerializer.string()来序列化Key。注意此处使用的是Junit5。


这样就解决问题了吗?没有。因为RedisTemplate的Value也是采用默认的序列化类,也要进行统一修改。


因此上面的方法变成如下:


@BeforeEach

void init() {

redisTemplate.setKeySerializer(RedisSerializer.string());

redisTemplate.setValueSerializer(RedisSerializer.string());

}


小结

经过上述步骤,关于SpringBoot中混合使用StringRedisTemplate和RedisTemplate的坑已经填平了。


关于《SpringBoot视频教程全家桶》的视频课程第一阶段已经录制完成,目前108节课程。后续会不断新增其他实战场景、组件的内容。同时也会不断补充像本篇文章这样的实战经验。为庆祝第一阶段告一段落,目前半价出售,大家多多支持。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
5月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
138 0
|
5月前
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
146 0
|
5月前
|
XML Java 数据库连接
Spring Boot的数据访问之Spring Data JPA以及Hibernate的实战(超详细 附源码)
Spring Boot的数据访问之Spring Data JPA以及Hibernate的实战(超详细 附源码)
92 0
|
10天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
22 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
22天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
42 2
|
4月前
|
运维 Java 关系型数据库
Spring运维之boot项目bean属性的绑定读取与校验
Spring运维之boot项目bean属性的绑定读取与校验
50 2
|
4月前
|
存储 运维 Java
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
56 2
|
4月前
|
Java Maven
springboot项目打jar包后,如何部署到服务器
springboot项目打jar包后,如何部署到服务器
382 1
|
4月前
|
XML 运维 Java
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
47 1
|
4月前
springboot2.4.5使用pagehelper分页插件
springboot2.4.5使用pagehelper分页插件
134 0