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

在《SpringBoot視頻教程全家桶》系列教程中,我們分別講解了StringRedisTemplate和RedisTemplate的使用和區別。

但在實踐中,有朋友遇到這樣的問題,就是存儲到Redis數據取不到值。

兩種Template的源碼分析

這是為什麼呢?是因為他同時使用了StringRedisTemplate和RedisTemplate在Redis中存儲和讀取數據。它們最重要的一個區別就是默認採用的序列化方式不同(在課程中已經講到)。這裡我們再來回顧一下相關源碼,StringRedisTemplate的部分源碼如下:

<code>public class StringRedisTemplate extends RedisTemplate<string> {

\t/**
\t * Constructs a new <code>StringRedisTemplate/<code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
\t * and {@link #afterPropertiesSet()} still need to be called.
\t */
\tpublic StringRedisTemplate() {
\t\tsetKeySerializer(RedisSerializer.string());
\t\tsetValueSerializer(RedisSerializer.string());
\t\tsetHashKeySerializer(RedisSerializer.string());
\t\tsetHashValueSerializer(RedisSerializer.string());
\t}

}
/<string>/<code>

通過該源碼我們可以看到StringRedisTemplate採用的是RedisSerializer.string()來序列化Redis中存儲數據的Key的。

下面再來看看RedisTemplate中默認採用什麼形式來序列化對應的Key。

<code>public class RedisTemplate extends RedisAccessor implements RedisOperations, BeanClassLoaderAware {
// 省略其他源碼
\tprivate @Nullable RedisSerializer> defaultSerializer;
\tprivate @Nullable ClassLoader classLoader;


\t/*
\t * (non-Javadoc)
\t * @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet()
\t */
\t@Override
\tpublic void afterPropertiesSet() {

\t\tsuper.afterPropertiesSet();

\t\tboolean defaultUsed = false;

\t\tif (defaultSerializer == null) {

\t\t\tdefaultSerializer = new JdkSerializationRedisSerializer(
\t\t\t\t\tclassLoader != null ? classLoader : this.getClass().getClassLoader());
\t\t}

\t\tif (enableDefaultSerializer) {

\t\t\tif (keySerializer == null) {
\t\t\t\tkeySerializer = defaultSerializer;
\t\t\t\tdefaultUsed = true;
\t\t\t}
\t\t\t// 省略其他源碼
\t\t}

\t\tif (enableDefaultSerializer && defaultUsed) {
\t\t\tAssert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
\t\t}

\t\tif (scriptExecutor == null) {
\t\t\tthis.scriptExecutor = new DefaultScriptExecutor<>(this);
\t\t}

\t\tinitialized = true;
\t}
\t// 省略其他源碼

}
/<code>

我們可以看到RedisTemplate使用的序列化類為defaultSerializer,默認情況下為JdkSerializationRedisSerializer。如果未指定Key的序列化類,keySerializer與defaultSerializer採用相同的序列化類。

通過上述兩個Template的分析我們就可以看出它們在Redis存儲的Key,採用了不同的序列化方法。

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

還原測試

下面先看一個單元測試:

<code>@Slf4j
@SpringBootTest
class RedisDifferentTemplateTest {
\t@Resource
\tprivate RedisTemplate<string> redisTemplate;

\t@Resource
\tprivate StringRedisTemplate stringRedisTemplate;

\t@Test
\tvoid testSimple() {
\t\tredisTemplate.opsForValue().set("myWeb", "www.choupangxia.com");
\t\tAssertions.assertEquals("www.choupangxia.com", redisTemplate.opsForValue().get("myWeb"));

\t\tAssertions.assertEquals("www.choupangxia.com",stringRedisTemplate.opsForValue().get("myWeb"));
\t}
}
/<string>/<code>

在上述方法中先通過redisTemplate存儲一個key為myWeb的數據到Redis中,隨後通過redisTemplate獲取並判斷斷言,可以成功通過。但隨後通過stringRedisTemplate獲取同樣的key的值,則拋出異常,異常信息如下:

<code>org.opentest4j.AssertionFailedError: 
Expected :www.choupangxia.com
Actual :null
<click>

/<click>/<code>

也就是說獲取的結果為null。

那麼,我們再通過Redis客戶端看一下兩種形式存儲到redis中key的值的情況。

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

我們可以看到通過StringRedisTemplate存儲的數據Key為“myWeb”,而RedisTemplate存儲的Key為“\\\\xAC\\\\xED\\\\x00\\\\x05t\\\\x00\\\\x05myWeb”,這也就是為什麼默認情況下兩者存儲的數據沒辦法混合使用了。

解決方案

那麼,如果在生產環境中想通用StringRedisTemplate和RedisTemplate進行字符串的處理該怎麼辦?

此時就需要指定統一的Key的序列化處理類,比如在RedisTemplate序列化時指定與StringRedisTemplate相同的類。

在上述單元測試中添加如下方法:

<code>@BeforeEach
void init() {
\tredisTemplate.setKeySerializer(RedisSerializer.string());
}
/<code>

也就是設置RedisTemplate也使用RedisSerializer.string()來序列化Key。注意此處使用的是Junit5。

這樣就解決問題了嗎?沒有。因為RedisTemplate的Value也是採用默認的序列化類,也要進行統一修改。

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

<code>@BeforeEach
void init() {
\tredisTemplate.setKeySerializer(RedisSerializer.string());
\tredisTemplate.setValueSerializer(RedisSerializer.string());
}
/<code>

小結

經過上述步驟,關於SpringBoot中混合使用StringRedisTemplate和RedisTemplate的坑已經填平了。

本文首發於公眾號:程序新視界。更多精彩內容,請關注一下。


分享到:


相關文章: