spring @EnableCaching框架的强大不再多说,想必大家都很清楚了;
redis作为缓存服务器的首选也是众望所归
笔者的使用场景是缓存两种数据:
a.以用户id为key,缓存用户账号相关数据
b.以‘allNetworkAnchors’为key,缓存所有的主播账号数据
然后就开始愉快的编码了,
@Cacheable(value = RedisCachingConfig.CACHE_USER, key = "'allNetworkAnchors'")
public List<UserDTO> findAllNetworkAnchors() {
log.info("findAllNetworkAnchors from db ");
List<UserEntity> list = userService.list();
return UserConvertor.INSTANCE.toDTOs(list);
}
@Cacheable(value = RedisCachingConfig.CACHE_USER, key = "#id", unless = "#result == null")
public UserDTO findUserById(Long id) {
log.info("query from db ,id: {}", id);
UserEntity en = userService.getById(id);
return UserConvertor.INSTANCE.toDTO(en);
}
2020-05-20 15:16:33.301 [http-nio-18080-exec-4] INFO xx.xx.UserService - findAllNetworkAnchors from db
2020-05-20 15:19:14.388 [http-nio-18080-exec-8] INFO xx.xx.UserService - query from db ,id: 1
第二次调用findUserById,也没有问题,很自然的走了redis缓存,当调用findAllNetworkAnchors时,错误出现了
org.springframework.data.redis.serializer.SerializationException: Could not deserialize: syntax error, expect {, actual [, pos 0, fastjson-version 1.2.51; nested exception is com.alibaba.fastjson.JSONException: syntax error, expect {, actual [, pos 0, fastjson-version 1.2.51
at com.alibaba.fastjson.support.spring.FastJsonRedisSerializer.deserialize(FastJsonRedisSerializer.java:49)
at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:48)
at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.read(RedisSerializationContext.java:226)
从错误日志可以很明显的看出出错的原因:
redis中存储的是array格式的json,而反序列化时使用FastJsonRedisSerializer导致强转失败。
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
try {
return (T) JSON.parseObject(bytes, type, fastJsonConfig.getFeatures());
} catch (Exception ex) {
throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
}
}
public interface RedisSerializer<T> {
byte[] serialize(@Nullable T t) throws SerializationException;
@Nullable
T deserialize(@Nullable byte[] bytes) throws SerializationException;
....
}
可以看到上层接口已经限定了T,没有List的操作入口
3. 那我们在实现时,是否可以变通下,让deserialize返回的结果根据redis缓存的内容,来决定是array还是object呢?
直接上代码, 使用自定义的FastJsonArrayCompatibleRedisSerializer作为redis的序列化执行器,问题解决!
public static class FastJsonArrayCompatibleRedisSerializer implements RedisSerializer<Object> {
private static byte ARRAY_START_BYTE = '[';
private FastJsonConfig fastJsonConfig = new FastJsonConfig();
private Class<?> type;
public FastJsonArrayCompatibleRedisSerializer(Class<?> type) {
this.type = type;
}
public FastJsonConfig getFastJsonConfig() {
return fastJsonConfig;
}
public void setFastJsonConfig(FastJsonConfig fastJsonConfig) {
this.fastJsonConfig = fastJsonConfig;
}
@Override
public byte[] serialize(Object t) throws SerializationException {
if (t == null) {
return new byte[0];
}
try {
return JSON.toJSONBytes(t, fastJsonConfig.getSerializeConfig(), fastJsonConfig.getSerializerFeatures());
} catch (Exception ex) {
throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);
}
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
try {
if (isArray(bytes[0])) {
return JSON.parseArray(new String(bytes), type);
}
return JSON.parseObject(bytes, type, fastJsonConfig.getFeatures());
} catch (Exception ex) {
throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
}
}
/**
* 判断第一个字符是否是‘[’
* @param firstByte
* @return
*/
private boolean isArray(int firstByte) {
return ARRAY_START_BYTE == firstByte;
}
}