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的值的情况。
我们可以看到通过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节课程。后续会不断新增其他实战场景、组件的内容。同时也会不断补充像本篇文章这样的实战经验。为庆祝第一阶段告一段落,目前五折出售,大家多多支持。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接
本文链接:https://choupangxia.com/2020/03/11/spring-boot-stringredistemplate-redistemplate/