learn-tech/专栏/Spring编程常见错误50例/18SpringData常见错误.md
2024-10-16 06:37:41 +08:00

16 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        18 Spring Data 常见错误
                        你好,我是傅健。

上一章节我们学习了 Spring Web 开发的常见错误。那么从这节课开始,我们将重点关注其他的一些 Spring 工具使用上的错误。

实际上,除了 Spring Web 外Spring 还提供了很多其他好用的工具集Spring Data 就是这样的存在。众所周知,基本上所有的项目都会用到数据库,所以 Spring 提供了对市场上主流数据库的贴心支持,我们不妨通过下面的列表快速浏览下:

Spring Data Commons- Spring Data JPA- Spring Data KeyValue- Spring Data LDAP- Spring Data MongoDB- Spring Data Redis- Spring Data REST- Spring Data for Apache Cassandra- Spring Data for Apache Geode- Spring Data for Apache Solr- Spring Data for Pivotal GemFire- Spring Data Couchbase (community module)- Spring Data Elasticsearch (community module)- Spring Data Neo4j (community module)

而在你使用这些各种各样的数据库时难免会遇到问题接下来我会选取3个典型案例为你总结下那些高频问题。

案例 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 {

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