在上一篇文章中,我们已经知道如何使用 Spring Data JPA 构建 Spring Boot Rest CRUD APIs。在本教程中,我将继续使用 Spring Data JPA 和Pageable制作服务器端分页和过滤器。
Spring Boot 分页和过滤器示例概述
使网站友好的最重要的事情之一是响应时间,而分页就是这个原因。例如,这个 bezkoder.com 网站有数百个教程,我们不想一次看到所有教程。分页是指以一页为单位显示全部的一小部分。
假设我们在数据库中有tutorials表,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/a886a896523d8147cadd221ef36ba32d.png)
以下是一些用于分页的 url 示例(带/不带过滤器):
/api/tutorials?page=1&size=5
/api/tutorials?size=5
: 使用页面的默认值/api/tutorials?title=data&page=1&size=3
:按包含“数据”的标题进行分页和过滤/api/tutorials/published?page=2
:按“已发布”状态进行分页和过滤
这是我们希望从 API 获得的服务器端分页结果的结构:
{
"totalItems": 8,
"tutorials": [...],
"totalPages": 3,
"currentPage": 1
}
阅读具有默认页面索引 (0) 和页面大小 (3) 的教程:
指示页面索引 = 2,但未指定总共 8 个项目的大小(默认值:3):
- page_0:3 项
- page_1:3 项
- page_2:2 项
指示 size = 5 但不指定页面索引(默认值:0):
![](https://i-blog.csdnimg.cn/blog_migrate/3fedc3e39d5ca3e3e2a1e8fb79d83ae2.png)
对于页面索引 = 1 和页面大小 = 5(总共 8 项):
按包含字符串的标题进行分页和过滤:
![](https://i-blog.csdnimg.cn/blog_migrate/3a3ec8542feb5bf67ec73638b9204062.png)
按发布状态分页和过滤:
使用 Spring Data JPA 进行分页和过滤
为了帮助我们处理这种情况,Spring Data JPA 提供了使用PagingAndSortingRepository实现分页的方法。
PagingAndSortingRepository
扩展CrudRepository以提供额外的方法来使用分页抽象检索实体。
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Page<T> findAll(Pageable pageable);
}
findAll(Pageable pageable)
:返回Page
满足对象提供的分页条件的实体Pageable
。
Spring Data 还支持从方法名称创建许多有用的查询,我们将在此示例中使用这些名称来过滤结果,例如:
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);
您可以在此处的方法名称中找到更多受支持的关键字。
要使用分页对多个字段进行排序,请访问教程:
Spring Data JPA 按多列排序/排序 | 弹簧靴
春季数据页面
让我们看一下Page对象。
Page
是Slice
带有几个附加方法的子接口。它包含整个列表的元素总数和总页数。
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty();
static <T> Page<T> empty(Pageable pageable);
long getTotalElements();
int getTotalPages();
<U> Page<U> map(Function<? super T,? extends U> converter);
}
如果项目数量增加,性能可能会受到影响,是时候考虑Slice了。
一个Slice
对象比 a 知道的信息少Page
,例如,下一个或前一个是否可用,或者这个切片是第一个/最后一个。当您不需要项目总数和总页数时,您可以使用它。
public interface Slice<T> extends Streamable<T> {
int getNumber();
int getSize();
int getNumberOfElements();
List<T> getContent();
boolean hasContent();
Sort getSort();
boolean isFirst();
boolean isLast();
boolean hasNext();
boolean hasPrevious();
...
}
Spring 数据可分页
现在我们将在上面的 Repository 方法中看到Pageable参数。Spring Data 基础设施将自动识别此参数以将分页和排序应用于数据库。
该Pageable
界面包含有关所请求页面的信息,例如页面的大小和数量。
public interface Pageable {
int getPageNumber();
int getPageSize();
long getOffset();
Sort getSort();
Pageable next();
Pageable previousOrFirst();
Pageable first();
boolean hasPrevious();
...
}
所以当我们想在结果中获得分页(有或没有过滤器)时,我们只需添加Pageable
到方法的定义中作为参数。
Page<Tutorial> findAll(Pageable pageable);
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);
这就是我们使用实现接口的PageRequestPageable
类创建对象的方式:Pageable
Pageable paging = PageRequest.of(page, size);
page
:从零开始的页面索引,不得为负数。size
:要返回的页面中的项目数,必须大于0。
创建 Spring Boot 应用程序
你可以一步一步来,或者在这篇文章中获取源代码:
Spring Boot, Spring Data JPA – Rest CRUD API example
Spring 项目包含我们只需要添加一些更改以使分页正常工作的结构。
或者您可以在本教程的最后获取新的 Github 源代码(包括分页和排序)。
数据模型
我们有这样的教程实体:
package com.bezkoder.spring.data.jpa.pagingsorting.model;
import javax.persistence.*;
@Entity
@Table(name = "tutorials")
public class Tutorial {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name = "title")
private String title;
@Column(name = "description")
private String description;
@Column(name = "published")
private boolean published;
public Tutorial() {
}
public Tutorial(String title, String description, boolean published) {
this.title = title;
this.description = description;
this.published = published;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isPublished() {
return published;
}
public void setPublished(boolean isPublished) {
this.published = isPublished;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
}
}
支持分页和过滤器的存储库
在本教程的早期,我们知道PagingAndSortingRepository
,但在本示例中,为了保持连续性并利用 Spring Data JPA,我们继续使用扩展接口的JpaRepository 。PagingAndSortingRepository
package com.bezkoder.spring.data.jpa.pagingsorting.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import com.bezkoder.spring.data.jpa.pagingsorting.model.Tutorial;
public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
Page<Tutorial> findByPublished(boolean published, Pageable pageable);
Page<Tutorial> findByTitleContaining(String title, Pageable pageable);
}
pageable
在上面的代码中,我们使用带有 Spring Query Creation的add参数来查找所有标题包含输入字符串的教程。
更多派生查询:
Spring Boot 中的 JPA 存储库查询示例
带@Query
注释的自定义查询:
Spring JPA @Query 示例:Spring Boot 中的自定义查询
带有分页和过滤器的控制器
通常,在 HTTP 请求 URL 中,分页参数是可选的。因此,如果我们的 Rest API 支持服务器端分页,我们应该提供默认值以使分页工作,即使客户端没有指定这些参数。
package com.bezkoder.spring.data.jpa.pagingsorting.controller;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
...
@RestController
@RequestMapping("/api")
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity<Map<String, Object>> getAllTutorials(
@RequestParam(required = false) String title,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size
) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
Pageable paging = PageRequest.of(page, size);
Page<Tutorial> pageTuts;
if (title == null)
pageTuts = tutorialRepository.findAll(paging);
else
pageTuts = tutorialRepository.findByTitleContaining(title, paging);
tutorials = pageTuts.getContent();
Map<String, Object> response = new HashMap<>();
response.put("tutorials", tutorials);
response.put("currentPage", pageTuts.getNumber());
response.put("totalItems", pageTuts.getTotalElements());
response.put("totalPages", pageTuts.getTotalPages());
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/published")
public ResponseEntity<Map<String, Object>> findByPublished(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size
) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
Pageable paging = PageRequest.of(page, size);
Page<Tutorial> pageTuts = tutorialRepository.findByPublished(true, paging);
tutorials = pageTuts.getContent();
Map<String, Object> response = new HashMap<>();
response.put("tutorials", tutorials);
response.put("currentPage", pageTuts.getNumber());
response.put("totalItems", pageTuts.getTotalElements());
response.put("totalPages", pageTuts.getTotalPages());
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
...
}
@RequestParam
在上面的代码中,我们使用注释来接受分页参数page
, size
。默认情况下,3
教程将从页面索引中的数据库中获取0
。
接下来,我们用&创建一个Pageable
对象。然后检查参数是否存在。page
size
title
- 如果为null,我们调用Repository
findAll(paging)
,分页就是Pageable
上面的对象。 - 如果客户端使用 发送请求,请
title
使用findByTitleContaining(title, paging)
。
两种方法都返回一个Page
对象。我们称之为:
getContent()
检索页面中的项目列表。getNumber()
当前页面。getTotalElements()
对于存储在数据库中的总项目。getTotalPages()
总页数。
结论
在这篇文章中,我们学习了如何使用 Spring Data JPA、Page 和 Pageable 接口在 Spring Boot 应用程序中创建分页和过滤结果。
我们还看到它JpaRepository
支持一种无需样板代码即可制作服务器端分页和过滤方法的好方法。
源代码
您可以在Github上找到本教程的完整源代码。