Spring Data JPA 中的 Scroll API

1、概览

Spring Data Commons 是总括 Spring Data project 的一部分,其中包含管理持久层的接口和实现。Scroll API 是 Spring Data Commons 提供的功能之一,用于处理从数据库读取的大型结果。

在本教程中,我们将通过一个示例探索 Scroll API。

2、依赖

Spring Boot 3.1 版本新增了 Scroll API 支持。Spring Data Commons 已包含在 Spring Data JPA 中。因此,添加 Spring Data JPA 3.1 版本就可以获得 Scroll API 功能:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>3.1.0</version>
</dependency>

最新的版本可在 Maven 中央仓库 中找到。

3、 Entity 类

例如,我们将使用 BookReview 实体,它包含不同用户对不同书籍的书评评级:

@Entity
@Table(name="BOOK_REVIEWS")
public class BookReview {
    @Id
    @GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "book_reviews_reviews_id_seq")
    @SequenceGenerator(name = "book_reviews_reviews_id_seq", sequenceName = "book_reviews_reviews_id_seq", allocationSize = 1)
    private Long reviewsId;
    private String userId;
    private String isbn;
    private String bookRating;

    // getter 和 setter 方法
}

4、Scroll API

Scroll API 提供了分块迭代大型结果的功能。它提供稳定的排序、scroll 类型和结果限制。

我们可以使用属性名称定义简单的排序表达式,并通过查询推导使用 TopFirst 定义静态结果限制。

4.1、使用 Offset 过滤

在下面的示例中,我们使用查询派生功能,根据评分参数和 OffsetScrollPosition 查找前五本图书:

public interface BookRepository extends Repository<BookReview, Long> {
    Window<BookReview> findFirst5ByBookRating(String bookRating, OffsetScrollPosition position);
    Window<BookReview> findFirst10ByBookRating(String bookRating, OffsetScrollPosition position);
    Window<BookReview> findFirst3ByBookRating(String bookRating, KeysetScrollPosition position);
}

定义好了 repository 方法,我们就可以在业务类中使用它们来获取前五本图书,并不断迭代直到得到最后一个结果。

在迭代过程中,我们需要通过查询来检查下一个 Window 是否存在:

public List<BookReview> getBooksUsingOffset(String rating) {
    OffsetScrollPosition offset = ScrollPosition.offset();

    Window<BookReview> bookReviews = bookRepository.findFirst5ByBookRating(rating, offset);
    List<BookReview> bookReviewsResult = new ArrayList<>();
    do {
        bookReviews.forEach(bookReviewsResult::add);
        bookReviews = bookRepository.findFirst5ByBookRating(rating, (OffsetScrollPosition) bookReviews.positionAt(bookReviews.size() - 1));
    } while (!bookReviews.isEmpty() && bookReviews.hasNext());

    return bookReviewsResult;
}

我们可以使用 WindowIterator 来简化我们的逻辑,通过使用这个工具类,可以不用检查下一个 window 和 ScrollPosition

public List<BookReview> getBooksUsingOffSetFilteringAndWindowIterator(String rating) {
    WindowIterator<BookReview> bookReviews = WindowIterator.of(position -> bookRepository
      .findFirst5ByBookRating("3.5", (OffsetScrollPosition) position)).startingAt(ScrollPosition.offset());
    List<BookReview> bookReviewsResult = new ArrayList<>();
    bookReviews.forEachRemaining(bookReviewsResult::add);

    return bookReviewsResult;
}

Offset Scroll 的工作原理类似于分页,通过跳过大结果中一定数量的记录来返回预期结果。虽然我们只能看到所请求结果的一部分,但服务器却需要构建完整的结果,这就造成了额外的负载。

我们可以使用 keyset 过滤功能来避免这种行为。

4.2、使用 Keyset 过滤

Keyset 过滤有助于利用数据库的内置功能检索结果子集,从而减少单个查询的计算量和 IO 需求。

数据库只需要从给定的 keyset 位置构建较小的结果,而不需要生成大量的完整结果:

public List<BookReview> getBooksUsingKeySetFiltering(String rating) {
    WindowIterator<BookReview> bookReviews = WindowIterator.of(position -> bookRepository
      .findFirst5ByBookRating(rating, (KeysetScrollPosition) position))
      .startingAt(ScrollPosition.keyset());
    List<BookReview> bookReviewsResult = new ArrayList<>();
    bookReviews.forEachRemaining(bookReviewsResult::add);

    return bookReviewsResult;
}

5、总结

在本文中,我们探讨了 Spring Data Commons 库提供的 Scroll API。Scroll API 支持根据偏移位置和筛选条件将大结果分成小块读取。

Scroll API 支持使用 offset 和 keyset 进行过滤。基于 offset 的过滤需要在数据库中具体化整个结果,而 keyset 则通过构建较小的结果来帮助减少数据库的计算和 IO 负载。


参考:https://www.baeldung.com/spring-data-jpa-scroll-api