Spring Data 常见错误

实际上,除了 Spring Web 外,Spring 还提供了很多其他好用的工具集,Spring Data 就是这样的存在。众所周知,基本上所有的项目都会用到数据库,所以 Spring 提供了对市场上主流数据库的贴心支持。

案例 1:注意读与取的一致性

当使用 Spring Data Redis 时,我们有时候会在项目升级的过程中,发现存储后的数据有读取不到的情况;另外,还会出现解析出错的情况。这里我们不妨直接写出一个错误案例来模拟下:

@SpringBootApplication
public class SpringdataApplication {

    SpringdataApplication(RedisTemplate redisTemplate,
            StringRedisTemplate stringRedisTemplate){
        String key = "mykey";
        stringRedisTemplate.opsForValue().set(key, "myvalue");

        Object valueGotFromStringRedisTemplate = stringRedisTemplate.opsForValue().get(key);
        System.out.println(valueGotFromStringRedisTemplate);

        Object valueGotFromRedisTemplate = redisTemplate.opsForValue().get(key);
        System.out.println(valueGotFromRedisTemplate);
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringdataApplication.class, args);
    }

}

在上述代码中,我们使用了 Redis 提供的两种 Template,一种 RedisTemplate,一种 stringRedisTemplate。但是当我们使用后者去存一个数据后,你会发现使用前者是取不到对应的数据的。输出结果如下:

myvalue

null

此时你可能会想,这个问题不是很简单么?肯定是这两个 Template 不同导致的。

没错,这是一个极度简化的案例,我们的学习目的是举一反三。你可以试想一下,如果我们是不同的开发者开发不同的项目呢?一个项目只负责存储,另外一个项目只负责读取,两个项目之间缺乏沟通和协调。这种问题在实际工作中并不稀奇,接下来我们就了解下这个问题背后的深层次原因。

案例解析

要了解这个问题,需要我们对 Spring Data Redis 的操作流程有所了解。

首先,我们需要认清一个现实:我们不可能直接将数据存取到 Redis 中,毕竟一些数据是一个对象型,例如 String,甚至是一些自定义对象。我们需要在存取前对数据进行序列化或者反序列化操作。

具体到我们的案例而言,当带着 key 去存取数据时,它会执行 AbstractOperations#rawKey,使得在执行存储 key-value 到 Redis,或从 Redis 读取数据之前,对 key 进行序列化操作:

byte[] rawKey(Object key) {

   Assert.notNull(key, "non null key required");

   if (keySerializer() == null && key instanceof byte[]) {
      return (byte[]) key;
   }

   return keySerializer().serialize(key);
}

从上述代码可以看出,假设存在 keySerializer,则利用它将 key 序列化。而对于

StringRedisSerializer 来说,它指定的其实是 StringRedisSerializer。具体实现如下:

public class StringRedisSerializer implements RedisSerializer<String> {

   private final Charset charset;

   @Override
   public byte[] serialize(@Nullable String string) {
      return (string == null ? null : string.getBytes(charset));
   }
 
}

而如果我们使用的是 RedisTemplate,则使用的是 JDK 序列化,具体序列化操作参考下面的实现:

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {

  
   @Override
   public byte[] serialize(@Nullable Object object) {
      if (object == null) {
         return SerializationUtils.EMPTY_ARRAY;
      }
      try {
         return serializer.convert(object);
      } catch (Exception ex) {
         throw new SerializationException("Cannot serialize", ex);
      }
   }
}

很明显,上面对 key 的处理,采用的是 JDK 的序列化,最终它调用的方法如下:

public interface Serializer<T> {
    void serialize(T var1, OutputStream var2) throws IOException;

    default byte[] serializeToByteArray(T object) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
        this.serialize(object, out);
        return out.toByteArray();
    }
}

你可以直接将"mykey"这个字符串分别用上面提到的两种序列化器进行序列化,你会发现它们的结果确实不同。这也就解释了为什么它们不能读取到"mykey"设置的"myvalue"。

至于它们是如何指定 RedisSerializer 的,,我们可以以 StringRedisSerializer 为例简单看下。查看下面的代码,它是 StringRedisSerializer 的构造器,在构造器中,它直接指定了 KeySerializer 为 RedisSerializer.string():

public class StringRedisTemplate extends RedisTemplate<String, String> {

   public StringRedisTemplate() {
      setKeySerializer(RedisSerializer.string());
      setValueSerializer(RedisSerializer.string());
      setHashKeySerializer(RedisSerializer.string());
      setHashValueSerializer(RedisSerializer.string());
   }
}

其中 RedisSerializer.string() 最终返回的实例如下:

public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);

案例修正

要解决这个问题,非常简单,就是检查自己所有的数据操作,是否使用了相同的 RedisTemplate,就是相同,也要检查所指定的各种 Serializer 是否完全一致,否则就会出现各式各样的错误。

相关推荐
bug菌¹33 分钟前
滚雪球学Oracle[3.1讲]:Oracle SQL基础
数据库·sql·oracle
任风雨36 分钟前
场景题1-设计redis的key和value的原则
数据库·redis·缓存
Hello Dam2 小时前
【文件增量备份系统】MySQL百万量级数据量分页查询性能优化
java·mysql·性能优化·springboot·深分页优化
IT学长编程2 小时前
计算机毕业设计 C语言学习辅导网站的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计·计算机毕业设计选题
林小果12 小时前
访问者模式
java·开发语言·设计模式·访问者模式
IT学长编程2 小时前
计算机毕业设计 办公用品管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·办公用品管理系统
call_me_wangcheng2 小时前
django的模型层介绍与配置
数据库·python·django
J老熊3 小时前
SpringBoot 源码解读与自动装配原理结合Actuator讲解
java·spring boot·后端·spring·面试·系统架构
人生匆匆3 小时前
bluefs _flush_range allocated: osd用空间但是显示ceph_bluefs_db_used_bytes is 100%
数据库·ceph·servlet
java_heartLake3 小时前
设计模式之备忘录模式
java·设计模式·备忘录模式