对于最新的稳定版本,请使用 Spring Data Commons 4.0.4spring-doc.cadn.net.cn

滚动

滚动是一种更细粒度的方法,用于迭代处理较大的结果集分块。 滚动包含稳定排序、滚动类型(基于偏移量或基于键集的滚动)以及结果限制。 您可以使用属性名定义简单的排序表达式,并通过查询派生使用 TopFirst 关键字 定义静态结果限制。 您可以连接多个表达式以将多个条件合并为一个表达式。spring-doc.cadn.net.cn

滚动查询返回一个 Window<T>,该对象允许获取元素的滚动位置,以便继续获取下一个 Window<T>,直到您的应用程序消费完整个查询结果。 与通过获取下一批结果来消费 Java Iterator<List<…>> 类似,查询结果滚动允许您通过 ScrollPosition 访问一个 Window.positionAt(…),如下例所示:spring-doc.cadn.net.cn

Window<User> users = repository.findFirst10ByLastnameOrderByFirstname("Doe", ScrollPosition.offset());
do {

  for (User u : users) {
    // consume the user
  }

  if (users.isLast() || users.isEmpty()) {
    break;
  }

  // obtain the next Scroll
  users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
} while (!users.isEmpty());

ScrollPosition 用于标识某个元素在整个查询结果中的确切位置。 查询执行时会将 position 参数视为排他性(exclusive),即结果将从给定位置之后开始返回。 ScrollPosition#offset()ScrollPosition#keyset()ScrollPosition 的两种特殊形式,用于指示滚动操作的起始位置。spring-doc.cadn.net.cn

上述示例展示了静态排序和限制。 您可以另外定义接受 Sort 对象的查询方法,以定义更复杂的排序顺序,或实现基于每个请求的排序。 类似地,通过提供一个 Limit 对象,您可以基于每个请求动态定义限制,而不是应用静态限制。 有关动态排序和限制的更多信息,请参阅查询方法详解spring-doc.cadn.net.cn

滚动遍历消费 Window 实例时,需要编写大量条件判断才能实现最优的数据库往返次数,而使用 WindowIterator 可以简化这一重复性任务。spring-doc.cadn.net.cn

WindowIterator 提供了一种实用工具,通过省去检查是否存在下一个 Window 以及应用 Window 的需要,从而简化了在多个 ScrollPosition 之间的滚动操作。spring-doc.cadn.net.cn

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(ScrollPosition.offset());

while (users.hasNext()) {
  User u = users.next();
  // consume the user
}

使用偏移量滚动

偏移量滚动(Offset scrolling)类似于分页,使用一个偏移量计数器来跳过一定数量的结果,让数据源仅返回从指定偏移量开始的结果。 这种简单机制避免了将大量结果发送到客户端应用程序。 然而,大多数数据库在服务器能够返回结果之前,需要先物化整个查询结果。spring-doc.cadn.net.cn

示例 1. 在 Repository 查询方法中使用 OffsetScrollPosition
interface UserRepository extends Repository<User, Long> {

  Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, OffsetScrollPosition position);
}

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(OffsetScrollPosition.initial()); (1)
1 从无偏移量开始,以包含位置 0 处的元素。

ScrollPosition.offset()ScrollPosition.offset(0L) 之间存在区别。 前者表示滚动操作的起始位置,并不指向任何具体的偏移量,而后者则标识结果中的第一个元素(位于位置 0)。 鉴于滚动具有排他性(exclusive),使用 ScrollPosition.offset(0) 会跳过第一个元素,并转换为偏移量 1spring-doc.cadn.net.cn

使用键集过滤进行滚动

基于偏移量(offset-based)的分页方式要求大多数数据库在服务器返回结果之前,必须先物化(materializing)整个查询结果。 因此,尽管客户端只看到所请求结果的一部分,但服务器仍需构建完整的查询结果,从而造成额外的负载。spring-doc.cadn.net.cn

键集过滤(Keyset-Filtering)方法通过利用数据库的内置功能来获取结果子集,旨在降低单个查询的计算和 I/O 开销。 该方法通过将一组键传递到查询中以维持滚动状态,从而有效地修改您的过滤条件。spring-doc.cadn.net.cn

Keyset 过滤(Keyset-Filtering)的核心思想是使用一个稳定的排序顺序来开始获取结果。 当你想要滚动到下一个数据块时,会获得一个 ScrollPosition,用于在已排序的结果中重建当前位置。 该 ScrollPosition 会捕获当前 Window 中最后一个实体的键集(keyset)。 为了执行查询,重建过程会重写查询条件子句,使其包含所有排序字段和主键,以便数据库能够利用潜在的索引来高效执行查询。 数据库只需从给定的键集位置构建一个规模小得多的结果集,而无需完整生成庞大的结果集后再跳过大量记录以到达特定偏移位置。spring-doc.cadn.net.cn

键集过滤(Keyset-Filtering)要求用于排序的键集属性必须为非空(non-nullable)。 此限制源于数据存储对比较运算符中null值的特定处理方式,以及需要针对已建立索引的数据源执行查询。 在可为空(nullable)的属性上使用键集过滤将导致意外结果。spring-doc.cadn.net.cn

在 Repository 查询方法中使用 KeysetScrollPosition
interface UserRepository extends Repository<User, Long> {

  Window<User> findFirst10ByLastnameOrderByFirstname(String lastname, KeysetScrollPosition position);
}

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(ScrollPosition.keyset()); (1)
1 从最开始处启动,且不应用额外的过滤。

键集过滤(Keyset-Filtering)在数据库包含与排序字段匹配的索引时效果最佳,因此静态排序非常适用。 使用键集过滤的滚动查询要求查询返回排序顺序中使用的属性,并且这些属性必须映射到返回的实体中。spring-doc.cadn.net.cn

您可以使用接口和 DTO 投影,但请确保包含所有用于排序的属性,以避免键集(keyset)提取失败。spring-doc.cadn.net.cn

在指定您的 Sort 排序顺序时,只需包含与查询相关的排序属性即可; 如果您不希望确保查询结果的唯一性,则无需特别处理。 键集(keyset)查询机制会通过在排序顺序中加入主键(或复合主键中剩余的部分)来确保每个查询结果都是唯一的。spring-doc.cadn.net.cn