SpringBoot混合使用StringRedisTemplate和RedisTemplate的坑及解决

 更新时间:2023年12月20日 16:01:18   作者:程序新视界  
这篇文章主要介绍了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

我们可以看到通过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的坑已经填平了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。 

相关文章

  • MyBatis框架搭建时依赖包引入异常的问题解决

    MyBatis框架搭建时依赖包引入异常的问题解决

    在使用IDEA环境搭建MyBatis框架时,可能会因为依赖包版本过低导致兼容性问题,本文就来介绍一下MyBatis框架搭建时依赖包引入异常的问题解决,感兴趣的可以来了解一下
    2024-10-10
  • Spring Boot集成Elasticsearch全过程

    Spring Boot集成Elasticsearch全过程

    这篇文章主要介绍了Spring Boot集成Elasticsearch全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-07-07
  • Java高级架构之FastDFS分布式文件集群详解

    Java高级架构之FastDFS分布式文件集群详解

    这篇文章主要介绍了Java高级架构之FastDFS分布式文件集群详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-04-04
  • 详解mall整合SpringBoot+MyBatis搭建基本骨架

    详解mall整合SpringBoot+MyBatis搭建基本骨架

    这篇文章主要介绍了详解mall整合SpringBoot+MyBatis搭建基本骨架,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Java责任链模式详解

    Java责任链模式详解

    责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它用于将请求的发送者和接收者解耦,使得多个对象都有机会处理这个请求,本文将详细介绍 Java 中的责任链模式,包括其概念、结构、实现方式以及应用案例等,需要的朋友可以参考下
    2023-05-05
  • Eclipse开发JavaWeb项目配置Tomcat的方法步骤

    Eclipse开发JavaWeb项目配置Tomcat的方法步骤

    本文主要介绍了Eclipse开发JavaWeb项目配置Tomcat的方法步骤,首先介绍eclipse开发JavaWeb项目需要配置的相关环境,使用tomcat软件在本地搭建服务器,然后再在eclipse环境下配置tomcat,感兴趣的可以了解一下
    2021-08-08
  • SpringBoot集成gRPC微服务工程搭建实践的方法

    SpringBoot集成gRPC微服务工程搭建实践的方法

    这篇文章主要介绍了SpringBoot集成gRPC微服务工程搭建实践的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • 详解JVM系列之对象的锁状态和同步

    详解JVM系列之对象的锁状态和同步

    锁和同步是java多线程编程中非常常见的使用场景。为了锁定多线程共享的对象,Java需要提供一定的机制来实现共享对象的锁定。当第二个线程进入同一个区域的时候,必须等待第一个线程解锁该对象。JVM是怎么做到的呢?快来一起看看吧。
    2021-06-06
  • Spring Boot 3.1中整合Spring Security和Keycloak的方法

    Spring Boot 3.1中整合Spring Security和Keycloak的方法

    本文介绍在最新的SpringBoot3.1版本之下,如何将Keycloak和Spring Security一起跑起来,文中结合实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2023-06-06
  • Java实现分布式锁的3种方法总结

    Java实现分布式锁的3种方法总结

    分布式锁是一种用于保证分布式系统中多个进程或线程同步访问共享资源的技术,同时它又是面试中的常见问题,所以我们本文就重点来看分布式锁的具体实现,希望对大家有所帮助
    2023-09-09

最新评论