Spring Data分页

介绍

呈现更大的数据集可能具有挑战性。Spring Data可以通过提供各种分页功能来减轻一些痛苦。

基本

假设您要显示这样的分页组件:

首先,您需要计算元素的总数,以便可以估计页面数。

Spring Data为此提供了一个特殊的抽象:

public interface Page<T> extends Slice<T> {
  // returns the number of all pages
  int getTotalPages();

  // returns the number of all elements
  long getTotalElements();

  // returns element on this page
  List<T> getContent();

  ...
}

页面界面通常附带可寻呼,用于选择包含最多一定数量元素的特定页面:

public interface Pageable {
  int getPageNumber();
  int getPageSize();
  ...
}

两者都可以在您的@Repository界面中轻松使用:

@Entity
public class Employee {

  @Id
  private Long id;
  private String name;
  private BigDecimal salary;
}

@Repository
public interface EmployeeRepository
  extends CrudRepository<Employee, Long> {

  Page<Employee> findAll(Pageable pageable);
}

然后获取某个页面可以像这样完成:

private EmployeeRepository repository;

...
int page = 1;
int pageSize = 3;
Pageable pageable = PageRequest.of(page, pageSize);
Page<Employee> secondPageWithUpTo3Elements =
  repository.findAll(pageable);

如果此方法已经扩展了 Jpa 存储库分页和排序存储库,则无需将此方法显式添加到存储库接口:

public interface JpaRepository<T, ID>
  extends PagingAndSortingRepository<T, ID> { ... }

public interface PagingAndSortingRepository<T, ID> {
  Page<T> findAll(Pageable pageable);
  ...
}

带分页的动态查询

现在想象一下,您不仅需要将结果拆分为多个页面,还需要根据各种标准(例如薪资范围)添加过滤:

可分页仅存储有关页面请求的信息,因此不能将其用于传递条件,但是,它可以与其他 API 一起使用。

使用规范分页

您可以通过扩展 JPA 规范演示器接口来使用基于 JPA 标准 API 的规范

public interface JpaSpecificationExecutor<T> {
  Page<T> findAll(Specification<T> spec, Pageable pageable);
  ...
}

@Repository
public interface EmployeeRepository
  extends JpaSpecificationExecutor<Employee> { ... }

在这里,您可以找到一个代码段,显示如何传递规范

public static Specification<Employee> hasSalaryBetween(
    BigDecimal from, BigDecimal to) {
  return (Specification<Employee>) (employee, query, builder) ->
      builder.between(employee.get("salary"), from, to);
}

...

PageRequest pageRequest = PageRequest.of(0, 5);
Specification<Employee> spec = hasSalaryBetween(
  new BigDecimal("4000"), new BigDecimal("11000"));
Page<Employee> result = repository.findAll(spec, pageRequest);

使用查询进行分页

或者,您可以使用 Querydsl,它可以使您的代码更加类型安全,并且它还接受 Pageable 作为参数:

public interface QuerydslPredicateExecutor<T> {
  Page<T> findAll(Predicate predicate, Pageable pageable);
  ...
}

为了获得与以前相同的结果,您可以编写如下代码:

@Repository
public interface EmployeeRepository extends
  QuerydslPredicateExecutor<Employee> { ... }

...

QEmployee employee = QEmployee.employee;
Predicate predicate = employee.salary.between(
    new BigDecimal("4000"), new BigDecimal("11000"));
PageRequest pageRequest = PageRequest.of(0, 5);
Page<Employee> result = repository.findAll(predicate, pageRequest);

避免分页

如果您的数据库变得足够大,则可能会遇到这样一种情况,即计数匹配元素的成本相当高(尤其是在基本分页的基础上应用动态筛选时)。而且您应该知道,计算页数需要知道总行数。简而言之,创建页面对象需要执行两个 SQL 查询。

例如,要检索第三页,需要运行以下查询:

-- fetch the third page (pageSize=5)
SELECT *
FROM employee
WHERE ... -- some criteria
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;

-- count all matching elements
-- (could become your bottleneck)
SELECT COUNT(*)
FROM employee
WHERE ...; -- the same criteria as above

第一个 SELECT 应该完成得相当快,因为它期望得到少量结果。但是,假设您有一个巨大的数据集,则第二个查询可能会显著减慢当前页面的检索速度。问题在于,它需要对所有匹配的行进行计数才能获得精确的总值。

您的用户不太可能欣赏知道有1771571534元素可供浏览的价值:

切片作为替代方案

幸运的是,还有另一个名为 Slice 的有用抽象可以用于此方案:

public interface Slice<T> {
  int getNumberOfElements(); // on this Slice
  List<T> getContent();
  boolean hasNext();
  Pageable nextPageable(); // get next Slice
  ...
}

实际上,您可能已经注意到Page是从Slice派生的,但请注意,它们的实现可能会有很大差异。最重要的是,Slice不涉及我们刚刚讨论的昂贵的COUNT操作。

下一个切片的加载可以在单击“加载更多”按钮后调用:

...或者当用户到达页面末尾(通常称为无限滚动)::

要添加返回切片的方法,您只需编写:

@Repository
public interface EmployeeRepository extends ... {
  Slice<Employee> findAll(Pageable pageable);
}

如果您想保留返回 Page s 的方法,请在此处解决方法名称冲突:

// you would like to preserve this
Page<Employee> findAll(Pageable pageable);

// ...so you need to give this method a different name
@Query("SELECT e FROM Employee e")
Slice<Employee> findAllSliced(Pageable pageable);

调用此方法的代码几乎与以前相同。唯一的区别是它返回切片而不是页面

结束

有关一组完整的示例,请查看以下 GitHub 存储库

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值