在《SpringBoot 视频教程全家桶 https://edu.csdn.net/course/detail/20369 》系列教程中, 我们分别讲解了 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 视频教程全家桶 https://edu.csdn.net/course/detail/20369 》的视频课程第一阶段已经录制完成, 目前 108 节课程. 后续会不断新增其他实战场景, 组件的内容. 同时也会不断补充像本篇文章这样的实战经验. 为庆祝第一阶段告一段落, 目前半价出售, 大家多多支持.
来源: https://www.qcloud.com/developer/article/1600541