spring 我们是否可以先验证该高速缓存中的现有数据,然后在Sping Boot Cache中查找缺少的键?

btqmn9zl  于 2022-11-21  发布在  Spring
关注(0)|答案(1)|浏览(104)

我想从表中提取数据。table Name:学生

Student {
  int id;
  String Name;
};

我曾质问:

select Name from Student where id in (:ids);

需要:当我调用存储库student findByIdIn(:ids)时,我希望获得具有匹配id的学生,并缓存它们。如果我再次使用另一组id进行调用,则流程将如下所示:首先,它将检查该高速缓存中是否存在任何id数据,然后从缓存中获取这些数据,然后针对缓存中未获得的id进行DB调用,然后将其存储在缓存中。
期望 Spring Boot 缓存相关信息达到上述要求。

e0bqpujr

e0bqpujr1#

这个问题的简短答案是否定的。
如果我正确理解了您的问题(并进行了验证),那么您将看到类似以下场景的内容:
给定Student ID [ 2, 3, 4, 5, 6 ]的查询,并假定Student ID [ 2, 4, 6 ]在该高速缓存中,但Student ID [ 3, 5 ]当前仅在数据库中,则您将查找类似于以下内容的内容:

// PROBLEM 2
//@Cacheable("Students")
List<Student> findByIds(Set<Integer> studentIds) {

    List<Student> result = new ArrayList<>();

    // First load Students stored in Cache
    studentIds.stream()
        .map(id -> this.getCache().get(id))  // CACHE CALL - PROBLEM 3
        .filter(Objects::nonNull)
        .map(Cache.ValueWrapper::get)
        .filter(Student.class::isInstance)
        .map(Student.class::cast)
        .forEach(result::add);

    Set<Integer> idsToQuery = new HashSet<>(studentIds);

    idsToQuery.removeAll(result.stream()
        .map(Student::getId)
        .collect(Collectors.toSet()));

    // Assert Student IDs that need to be queried and loaded from the 
    // Database vs. Student IDs present in the Cache
    getStudentIdAssertion().accept(idsToQuery);

    // Run: SELECT * FROM Students WHERE id IN (...)
    getDatabase().query(idsToQuery).stream()
        .map(this::cache) // CACHE DATABASE QUERY RESULTS
        .forEach(result::add);

    return result;
}

参见完整的测试类here
Spring的Cache Abstraction是核心Spring框架的一部分,并且仅由Sping Boot 扩展(例如,带有自动配置和一些额外功能),它只是用缓存行为装饰或 Package 现有的和昂贵的服务方法或数据访问调用(使用AOP)。换句话说,它要么全有,要么全无。
简单地说,如果你有@Cacheable服务(或仓库/ DAO)方法,就像这样:

@Cache("Students")
List<Student> findById(Set<Integer> ids) {
  // perform other processing as necessary
  return repository.findByIds(ids);
}

然后,Spring的缓存安排相对于您的用例存在几个问题。
问题一:
首先,findById(..)服务方法要么全有,要么全无。参见问题2)该高速缓存中或不在高速缓存中。
如果KEY在该高速缓存中,则整个findById(..)服务方法甚至不会被调用。
如果KEY不在该高速缓存中,那么将调用findById(..)服务方法,但是在处理服务方法的过程中不再涉及“Students”Cache,除非您自己显式地涉及缓存,正如我在上面演示的那样(但这是另一个问题...问题3)。
注意:Spring的缓存行为大致等同于Map.computeIfAbsent(:KEY, :Function<KEY, VALUE>)
问题二:
默认情况下(参见doc),Spring的Cache Abstraction使用所有方法参数来生成一个键。当然,您可以自定义键的生成,但在这种情况下这对您没有帮助。
因此,通过调用@Cacheable findByIds(:Set<Integer>):List<Student>,实际缓存在“Students”Cache中的内容如下:

CACHE KEY    | CACHE VALUE
-------------|--------------
Set<Integer> | List<Student>

也就是说,将有一个缓存条目,其中整个ID集是KEY,值是整个结果集,或返回的学生列表。
相反,您可能希望在使用一组ID(例如[1, 2, 3])进行查询时,将每个单独的Student(对象/记录)单独存储该高速缓存中:

CACHE KEY    | CACHE VALUE
-------------|--------------
1            | Jon Doe
2            | Jane Doe
3            | Pie Doe
...

Spring不支持这种开箱即用的方式,而是it is possible to achieve
问题三:
正如您在上面的通用解决方案中所看到的,Spring的Cache接口是围绕现有缓存提供程序(实际)缓存实现的 Adapter,它只允许单键访问。
这对于每个关键字来说可能是相当昂贵的调用,这取决于1)关键字查询的数量(例如,10对1000个关键字可能是相当大的差别)和2)该高速缓存拓扑(本地对客户端/服务器对WAN等)。
理想情况下,您可以将所有键传递给Cache GET操作,虽然Spring的Cache Abstraction通常不支持这种操作,但底层的缓存提供程序缓存实现通常支持这种操作。
例如,在Hazelcast中,Cache实现是[Distributed]IMap(取决于您的拓扑和配置),因此,在获取对“本机”缓存的访问权限时,您可以调用IMap.getAll(:Set<K>)
然而,这也是非常特定和依赖于缓存提供程序的,因此在将应用程序耦合到底层缓存提供程序API时,必须小心,特别是在更改缓存提供程序时。但是,它确实允许您改进我上面介绍的通用解决方案。
结论:
总而言之,Spring的Cache Abstraction不允许你做你在这里要求的事情,但是,也许使用一个自定义的AOP方面,或者仅仅创建一个自定义的DAO Package 你的SD仓库,你可以达到类似的效果。
我将把它作为一个练习留给您,因为此UC中的里程因您的应用程序要求(尤其是性能要求)而有很大的不同。
祝你好运!

相关问题