乐趣区

记录Redis序列化的坑-存Long取Integer的类型转换错误问题及String对象被转义的问题

背景
最近遇到了两个 Redis 相关的问题,趁着清明假期,梳理整理。
1. 存入 Long 类型对象,在代码中使用 Long 类型接收,结果报类型转换错误。
2.String 对象的反序列化问题,直接在 Redis 服务器上新增一个 key-value,而后在代码中 get(key) 时,报反序列化失败。
Long 类型接收返回值报错的问题
Redis 的配置如下
Redis 中序列化相关的配置,我这里采用的是 GenericJackson2JsonRedisSerializer 类型的序列化方式(这种方式会有一个类型转换的坑,下面会提到)
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfiguration {

@Bean
public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
存入 Long 对象取出 Integer 对象
测试方法如下
@Test
public void redisSerializerLong(){
try {
Long longValue = 123L;
redisLongCache.set(“cacheLongValue”,longValue);
Object cacheValue = redisLongCache.get(“cacheLongValue”);
Long a = (Long) cacheValue;
}catch (ClassCastException e){
e.printStackTrace();
}
}

会报类型转换错误 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long。
为什么类型会变为 Integer 呢?跟我一起追踪源码,便会发现问题。
1. 在代码的最外层获取 redis 中 key 对应的 value 值
redisTemplate.opsForValue().get(key);
2. 在 DefaultValueOperations 类中的 get(Object key) 方法
public V get(Object key) {

return execute(new ValueDeserializingRedisCallback(key) {

@Override
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
return connection.get(rawKey);
}
}, true);
}
3. 打断点继续往里跟,RedisTemplate 中的 execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) 方法里面,有一行关键代码。
T result = action.doInRedis(connToExpose);
此为获取 redis 中对应的 value 值,并对其进行反序列化操作。
4. 在抽象类 AbstractOperations<K, V> 中,定义了反序列化操作,对查询结果 result 进行反序列化。
public final V doInRedis(RedisConnection connection) {
byte[] result = inRedis(rawKey(key), connection);
return deserializeValue(result);
}
V deserializeValue(byte[] value) 反序列化
V deserializeValue(byte[] value) {
if (valueSerializer() == null) {
return (V) value;
}
return (V) valueSerializer().deserialize(value);
}
5. 终于到了具体实现类 GenericJackson2JsonRedisSerializer
public Object deserialize(@Nullable byte[] source) throws SerializationException {
return deserialize(source, Object.class);
}
实现反序列化方法,注意!这里统一将结果反序列化为 Object 类型,所以这里便是问题的根源所在,对于数值类型,取出后统一转为 Object, 导致泛型类型丢失,数值自动转为了 Integer 类型也就不奇怪了。
public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws SerializationException {

Assert.notNull(type,
“Deserialization type must not be null! Pleaes provide Object.class to make use of Jackson2 default typing.”);

if (SerializationUtils.isEmpty(source)) {
return null;
}

try {
return mapper.readValue(source, type);
} catch (Exception ex) {
throw new SerializationException(“Could not read JSON: ” + ex.getMessage(), ex);
}
}
String 对象转义问题
测试方法
@Test
public void redisSerializerString() {
try {
String stringValue = “abc”;
redisStringCache.set(“codeStringValue”, stringValue);
String cacheValue = redisStringCache.get(“codeStringValue”);
// 序列化失败
String serverInsert = redisStringCache.get(“serverInsertValue”);
if (Objects.equals(cacheValue, serverInsert)) {
System.out.println(“serializer ok”);
} else {
System.out.println(“serializer err”);
}
} catch (Exception e) {
e.printStackTrace();
}
}
提前在 redis 服务器上插入一个非 Json 格式的 String 对象

直接在 Redis 服务器上使用 set 命令新增一对 Key-Value,在代码中取出会反序列化失败。
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Unrecognized token ‘abc’: was expecting (‘true’, ‘false’ or ‘null’)
at [Source: (byte[])”abc”; line: 1, column: 7]; nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token ‘abc’: was expecting (‘true’, ‘false’ or ‘null’)
at [Source: (byte[])”abc”; line: 1, column: 7]
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:132)
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:110)
at org.springframework.data.redis.core.AbstractOperations.deserializeValue(AbstractOperations.java:334)
at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:60)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:48)
总结
这个问题是因为,自己在测试的过程中,没有按照代码流程执行,想当然的认为,代码跑出来的结果和自己手动插入的结果是一样的。
在相关的测试验证过程中应该严格的控制变量,不能凭借下意识的决断来操作,谨记软件之事——必作于细!

退出移动版