Page<>和Slice<>在什么情况下使用?

48

我在Spring Jpa Data的文档中读到了有关从存储库创建动态查询时“分页”两种不同类型的对象的信息。

PageSlice

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable); 

所以,我尝试找到一些关于两者主要区别和不同用法的文章或任何东西,以及如何影响性能和排序的查询类型。

是否有人掌握这方面的知识、文章或其他好的信息来源?


10
我猜 Page 会触发额外的 count 查询来获取总页数或元素数,而 Slice 则不会。 - sp00m
4
我同意@sp00m的看法。实际上,从源代码来看,Page正在实现Slice。因此,从性能的角度考虑,当你需要总数时应该使用Page,否则使用Slice。 - Steffen Harbich
https://docs.spring.io/spring-data/jpa/docs/2.3.1.RELEASE/reference/html/#repositories.special-parameters - Rajat
1
您已经接受了一个没有解决“排序”问题的答案 - 比如说,当添加了排序操作时,切片和页面是如何行为的。您是否发现它们在涉及排序时有什么区别?我之所以这样问是因为排序也意味着需要读取所有记录。 - Victor
2个回答

56

Page 继承自 Slice,并触发一个计数查询以了解可用的元素总数和页面总数。根据 Spring Data JPA 文档所述:

Page 知道可用的元素总数和页面总数。它通过基础设施触发一个计数查询来计算总数。由于这可能会因存储方式不同而变得昂贵,因此可以使用 Slice 作为返回结果。当遍历较大的结果集时,Slice 只知道是否有下一步 Slice 可用,这可能就足够了。


2
是的,我已经看过了。我在想这个差异是否比那个更大,但我认为不是。那个答案似乎足够了:)谢谢大家。 - Emil Hotkowski
Slice has information about whether the next or previous object exists or not. I find it problematic when I used the Page - giannis christofakis
@cassiomolin,从仓库方法返回List<Entity>是否比Slice<Entity>和Page<Entity>更高效? - Mu-Majid
但是如果两者都按顺序排列,页面和切片之间有什么区别吗? - Victor

7
SlicePage的主要区别在于后者提供了非平凡的分页详细信息,例如记录总数(getTotalElements())、页面总数(getTotalPages())以及满足查询条件的下一页可用状态(hasNext()),而前者则仅与其对应的Page相比提供了下一页可用状态(hasNext())等分页详细信息。当你处理庞大的表格并且有大量记录时,Slice可以带来显著的性能优势。
让我们深入探讨两种变体的技术实现。
static class PagedExecution extends JpaQueryExecution {
    @Override
    protected Object doExecute(final AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {

        Query query = repositoryQuery.createQuery(accessor);
        return PageableExecutionUtils.getPage(query.getResultList(), accessor.getPageable(),
                () -> count(repositoryQuery, accessor));
    }

    private long count(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {

        List<?> totals = repositoryQuery.createCountQuery(accessor).getResultList();
        return (totals.size() == 1 ? CONVERSION_SERVICE.convert(totals.get(0), Long.class) : totals.size());
    }
}


如果您观察上面的代码片段,PagedExecution#doExecute方法会在底层调用PagedExecution#count方法来获取满足条件的记录总数。
    static class SlicedExecution extends JpaQueryExecution {
    @Override
    protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {

        Pageable pageable = accessor.getPageable();
        Query createQuery = query.createQuery(accessor);

        int pageSize = 0;
        if (pageable.isPaged()) {

            pageSize = pageable.getPageSize();
            createQuery.setMaxResults(pageSize + 1);
        }

        List<Object> resultList = createQuery.getResultList();

        boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;

        return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);

    }
}


如果您观察上面的代码片段,要查找下一组结果是否存在(对于 hasNext()),SlicedExecution#doExecute 方法总是获取多一个元素(createQuery.setMaxResults(pageSize + 1))并根据 pageSize 条件跳过它(hasNext ? resultList.subList(0, pageSize) : resultList)。
应用:
- 页面: - 当 UI/GUI 希望在搜索/查询的初始阶段就显示所有结果,并提供页面号码进行遍历时使用(例如带有页面号码的银行对账单)。 - 切片: - 当 UI/GUI 不希望在搜索/查询的初始阶段显示所有结果,而是打算根据滚动或点击下一个按钮事件来显示记录时使用(例如 Facebook 动态源搜索)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接