在使用RedisTemplate时,某些场景调用redis的自增函数,对redis的某个缓存值自增加一。
@Resource
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("k", 1);
System.out.println(valueOperations.get("k"));
valueOperations.increment("k");
System.out.println(valueOperations.get("k"));
}
但是运行上面代码,会报一下错误:
org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR value is not an integer or out of range
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
异常原因是ERR value is not an integer or out of range,由于k已经设置为1,那么肯定是redis里存储的value的值不是int类型。使用redis的可视化工具查看,存储数据如下:
可以断定由于RedisTemplate的序列化后的格式导致。打开RedisTemplate,在InitializingBean接口的afterPropertiesSet方法中,默认序列化类是JdkSerializationRedisSerializer。
JdkSerializationRedisSerializer 使用SerializingConverter对象转字节数组的转换器,将1序列化为上图的string值,导致redis无法对其自增自减。转换器底层序列化代码:
public interface Serializer<T> {
/**
* Write an object of type T to the given OutputStream.
* <p>Note: Implementations should not close the given OutputStream
* (or any decorators of that OutputStream) but rather leave this up
* to the caller.
* @param object the object to serialize
* @param outputStream the output stream
* @throws IOException in case of errors writing to the stream
*/
void serialize(T object, OutputStream outputStream) throws IOException;
/**
* Turn an object of type T into a serialized byte array.
* @param object the object to serialize
* @return the resulting byte array
* @throws IOException in case of serialization failure
* @since 5.2.7
*/
default byte[] serializeToByteArray(T object) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
serialize(object, out);
return out.toByteArray();
}
}
解决方法:
修改RedisTemplate的值序列化类,将序列化类从jdk序列化改为redis通用序列化类GenericToStringSerializer。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
// 1.创建 redisTemplate 模版
RedisTemplate<Object, Object> template = new RedisTemplate<>();
// 2.关联 redisConnectionFactory
template.setConnectionFactory(redisConnectionFactory);
// 3.设置 value 的转化格式和 key 的转化格式
template.setValueSerializer(new GenericToStringSerializer<>(Object.class));
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
GenericToStringSerializer在处理数字类型的时候,使用了ObjectToStringConverter的toString方法,将integer转为string,再使用getBytes转为字节数组。
final class ObjectToStringConverter implements Converter<Object, String> {
@Override
public String convert(Object source) {
return source.toString();
}
}
public byte[] serialize(@Nullable T object) {
if (object == null) {
return null;
} else {
String string = (String)this.converter.convert(object, String.class);
return string.getBytes(this.charset);
}
}