1.通常用法
@Resource
RedisTemplate redisTemplate;
public void doTest(){
redisTemplate.opsForValue().setIfAbset("key", "val", Duration.ofSecend(100));
}
RedisTemplate
表示操作的 key 和 val 都是String类型。
如果 val 是 Integer
@Resource
RedisTemplate redisTemplate;
public void doTest(){
redisTemplate.opsForValue().setIfAbset("key", 100, Duration.ofSecend(100));
}
这个就可能报错了 这是因为springboot访问redis时的序列化操作。
2.Serializer
springboot与Redis的交互是以二进制方式进行(byte[]
)。为了支持Java中的数据类型,就要对操作的对象(key,value,hashKey,hashValue...)做序列化操作。
redisTemplate 只为 key value hashKey hashValue 设置serializer
Springboot 提供了几个序列化的方法。
-
JdkSerializationRedisSerializer
默认 StringRedisSerializer
- 其他 或 自定义
JdkSerializationRedisSerializer
的序列化操作函数签名如下
public byte[] serialize(@Nullable Object object)
StringRedisSerializer
序列化操作函数签名如下
public byte[] serialize(@Nullable String string)
因为 StringRedisSerializer
只支持 string类型 所以如果使用RedisTemplate
就会报错。
如果使用 JdkSerializationRedisSerializer
则不仅支持 RedisTemplate
,同样能支持任意自定义类型 RedisTemplate
。
然而这种默认的序列化方式会导致redis中保存的key和value可读性较差,出现一些不可读的16进制字符
\xAC\xED\00\0x5t\x00
自定义序列化
JdkSerializationRedisSerializer
虽然在redis中保存的数据是不可读的,但是操作起来很方便。可直接指定返回值的类型,免去了再次转换之繁琐。
其实现原理是在redis中存储的数据里包含着数据类型。
有没有一种可能:在redis中数据不保存类型信息,通过为template指定value的类型,获取期望类型值。
即:使用StringRedisSerializer
,但能达到JdkSerializationRedisSerializer
的效果。
比如:
@Resource
RedisTemplate tplInt;
@Resource
RedisTemplate tplPerson;
public void testGet(){
Integer x = tplInt.get("valOfX");
Person p = tplPerson.get("valOfPerson");
}
难点在于:get()
内部是否知道外部期望的类型是什么?
答案是:没有办法通过代码传入,只能从获返回的数据中获取。 原因如下:
redis返回的是 byte[]
类型,要用一个 serializer
做反序列化。
RedisTemplate中的 serializer
得是一个bean,即是一个实例化的对象。这个对象要实现 RedisSerializer
接口,因此必定要绑定在一个固定类型上,如果是String
就不能是Integer
。所以无法根据需要传入。
-
StringRedisSerializer
实现的是RedisSerializer
-
JdkSerializationRedisSerializer
实现的是RedisSerializer
后者为了兼容所有类型,所以设置为Object
,反序列化后的数据是一个Object,这样就丢掉了原本的所有信息。所以如果要返回外部需要的类型,只能在序列化后做一次值的类型转换。本质逻辑类似如下:
public Object get(String key){
//get data from redis
return (Object)(new Person())
}
Person p = (Person)get();
如果为某个操作设置了专门的
serializer
,由于template
是单例的,其他线程也会受到影响。
其他坑
- 除了
StringRedisSerializer
,其他对字符串0的处理,会导致redis的incr类指令不可用。(int 0 正常)
3.结论
选择1
RedisTemplate
将就用
选择2
springboot的序列化可以自定义,自己搭配。比如
//默认改成 StringRedisSerializer,key类的原样
template.setDefaultSerializer(new StringRedisSerializer());
//为value支持复杂类型
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
template.setValueSerializer(jdkSerializer);
template.afterPropertiesSet();
这样 key、hashKey、hashValue可读的。value来支持复杂类型
4.参考
- spring-data-redis 序列化 https://www.cnkirito.moe/spring-data-redis-2/
- https://liuyueyi.github.io/hexblog/2018/06/11/180611-Spring%E4%B9%8BRedisTemplate%E9%85%8D%E7%BD%AE%E4%B8%8E%E4%BD%BF%E7%94%A8/