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 是否完全一致,否则就会出现各式各样的错误。

相关推荐
mldong7 分钟前
一个 App,十三套后端:手机审批端 uni-jeeflow-app 开源了
java·架构
风哥2号23 分钟前
数据库教程FGMT27‑MySQL数据库基础知识与体系架构
数据库·mysql
tqs_123451 小时前
MySQL RR隔离级别死锁|Gap间隙锁、临键锁,订单并发范围查询死锁根因方案
java
YangYang9YangYan1 小时前
2026 校招商品分析岗位 JD 拆解,核心指标、工具与面试考点
大数据·数据库·数据分析
逆境不可逃1 小时前
Pi Agent 学习笔记:多个工具怎样并行执行
java
Flynt2 小时前
Java 27 升级实测:默认值动得比新特性多,有个老参数会让 JVM 直接起不来
java·jvm·后端
Bs_MoneyMagnet2 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
西安栈上月明软件科技2 小时前
从业务黑话到本体图谱:OAG本体建模五步法(西安老系统AI化改造实战)
数据库·人工智能·架构
anxiao_m2 小时前
跨云跨机房大数据迁移怎么选?不同场景工具适配指南
数据库·云启快传
星云API技术支持3 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信