Spring Boot中混合使用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());
}
### 回答1: stringredistemplateredistemplate都是Redis的Java客户端库的类。它们都提供了操作Redis的方法,但是在具体的使用场景可能会有不同的选择。 stringredistemplate主要用于操作Redis的字符串类型数据,如set、get、incr等操作。而redistemplate则提供了更为通用的操作方法,可以操作Redis的各种数据类型,如hash、list、set、zset等。 因此,在使用Redis时,需要根据具体的业务需求选择合适的客户端库类。 ### 回答2: stringredistemplateredistemplate都是Spring Data Redis框架提供的Redis客户端操作模板。二者的主要区别在于操作的Redis数据类型不同。 stringredistemplate用于操作Redis的字符串类型数据,其定义了一些常用的字符串类型操作方法,比如set、get、append、increment等等。我们可以通过stringredistemplate来实现类似于缓存等场景下的读写操作。 redistemplate则可以操作Redis的所有数据类型,包括字符串、列表、哈希、集合等等。它提供了各种类型数据的操作方法,例如: - 支持Redis列表数据类型的leftPush、rightPush等 - 支持Redis哈希数据类型的put、delete等 - 支持Redis集合数据类型的add、remove、members等 redistemplate可以通过RedisCallback接口更加灵活地操作Redis,可以使用Lambda表达式或匿名内部类形式的实现RedisCallback接口的回调函数,进行复杂的Redis操作。 正因为stringredistemplateredistemplate都是Spring Data Redis的组件,因此使用起来都非常方便,并且相互兼容。如果需要仅操作字符串类型数据,那么可以只使用stringredistemplate;如果需要操作多种类型的Redis数据,那么可以使用redistemplate。同时,根据具体的业务场景和实际需要,在使用这两个操作模板时,我们应该根据具体需要来进行优化和选择,以达到更好的性能和效果。 ### 回答3: stringredistemplateredistemplate都是Redis的Java客户端,用于操作Redis数据库。 stringredistemplatespring-data-redis框架的一个模板对象,一般用于对Redis的字符串类型数据进行操作。其提供了一系列的方法如set、get、increment、append、getBit等用于对字符串数据进行增、删、改、查等操作。同时还为你内置了串行化器,在通过stringredistemplateRedis缓存写入数据时,将使用此内部串行化器将数据序列化成字节数组,以便在Redis存储数据。 redistemplate也是spring-data-redis框架的一个模板对象,与stringredistemplate类似,也提供了一系列的方法,以方便用户对Redis数据库进行数据操作。不过,与stringredistemplate不同的是,redistemplate可以对Redis所有类型的数据进行操作,包括字符串、哈希、列表、集合和有序集合、通用对象等多种类型。因此,redistemplate是一个更为通用的Redis客户端。 使用上,需要在应用程序配置redistemplate的实例,并且根据需要提供Redis连接池等配置。之后就可以通过redistemplate提供的方法对Redis数据库进行数据操作。对于stringredistemplate,其实例的创建方式和redistemplate类似,只需要区别在于实例对象的类型不同即可。 因此,从使用的角度来看,stringredistemplate主要适用于对Redis的字符串数据进行操作,而redistemplate则更加通用,适用于对Redis各种类型的数据进行操作。在实际开发,根据需要选择使用不同的模板对象,能够更好地提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值