为 Web 应用程序的用户,我们希望页面能够快速加载并且只显示 与我们相关的信息。对于显示项目列表的页面,这意味着 仅显示部分项目,而不是一次显示所有项目。
快速加载第一页后,UI 可以提供筛选器、 排序和分页,帮助用户快速找到他或 她正在寻找。
在本教程中,我们将检查 Spring Data 的分页支持,并创建如何使用的示例 并对其进行配置以及有关其如何在幕后工作的一些信息。
术语“分页”和“分页”通常用作同义词。他们的意思并不完全相同, 然而。在查阅了各种网络词典后,我拼凑出了以下定义, 我将在本文中使用:
分页是从数据库中加载一页又一页项目的行为,以便 保护资源。这就是本文大部分内容的内容。
分页是一个 UI 元素,它提供一系列页码,让用户选择 接下来要加载的页面。
在本教程中,我们使用 Spring Boot 来引导一个项目。您可以 通过使用Spring Initializr并选择以下依赖项来创建类似的项目:
我还用 JUnit 5 替换了 JUnit 4,以便生成的依赖项 看起来像这样(Gradle 表示法):
- dependencies {
- implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- implementation 'org.springframework.boot:spring-boot-starter-web'
- compileOnly 'org.projectlombok:lombok'
- annotationProcessor 'org.projectlombok:lombok'
- runtimeOnly 'com.h2database:h2'
- testImplementation('org.junit.jupiter:junit-jupiter:5.4.0')
- testImplementation('org.springframework.boot:spring-boot-starter-test'){
- exclude group: 'junit', module: 'junit'
- }
- }
Pageable无论我们想做传统的分页,无限滚动还是简单的“上一个” 和“下一个”链接,后端的实现是相同的。
如果客户端只想显示项目列表的“切片”,则需要提供 描述此切片的一些输入参数。在 Spring 数据中,这些参数捆绑在 界面。它提供了以下方法,其中包括(评论是我的):Pageable
- public interface Pageable {
-
- // number of the current page int getPageNumber();
-
- // size of the pages int getPageSize();
-
- // sorting parameters Sort getSort();
-
- // ... more methods }
每当我们只想加载完整项目列表的一部分时,我们都可以使用 ainstance 作为输入参数,因为它提供 要加载的页面编号以及页面大小。通过课堂, 它还允许定义要排序的字段及其方向 应排序(升序或降序)。PageableSort
创建实例的最常见方法是使用实现:PageablePageRequest
- Pageable pageable = PageRequest.of(0, 5, Sort.by(
- Order.asc("name"),
- Order.desc("id")));
这将为第一页创建一个请求,其中首先订购了 5 个项目 按名称(升序),按 ID (降序)。请注意,默认情况下页面索引是从零开始的!
困惑?java.awt.print.Pageable
使用时,您会注意到 IDE 有时会 建议进口代替春天的班。由于我们很可能不需要包中的任何类,因此我们可以 告诉我们的 IDE 完全忽略它。Pageablejava.awt.print.PageablePageablejava.awt
在IntelliJ中,转到设置中的“常规->编辑器->自动导入”,然后添加到标有“从导入和完成中排除”的列表中。java.awt.*
在 Eclipse 中,转到首选项中的“Java -> 外观 -> 类型过滤器”,然后 添加到包列表中。java.awt.*
PageSlice同时捆绑分页请求的输入参数,与接口 为返回给客户端的项目页面提供元数据(评论是我的):PageablePageSlice
- public interface Page
extends Slice{ -
- // total number of pages int getTotalPages();
-
- // total number of items long getTotalElements();
-
- // ... more methods
- }
- public interface Slice
{ -
- // current page number int getNumber();
-
- // page size int getSize();
-
- // number of items on the current page int getNumberOfElements();
-
- // list of items on this page List
getContent(); -
- // ... more methods
- }
通过接口提供的数据,客户端拥有所需的所有信息 以提供分页功能。Page
我们可以改用接口,如果我们不这样做 需要项目或页面的总数,例如,如果我们只想提供 “上一页”和“下一页”按钮,不需要“第一页”和“最后一页” 按钮。Slice
接口最常见的实现由类提供:PagePageImpl
- Pageable pageable = ...;
- List
listOfCharacters = ...; - long totalCharacters = 100;
- Page
page = - new PageImpl<>(listOfCharacters, pageable, totalCharacters);
如果我们想在 Web 控制器中返回一个(或)项,它需要接受 定义分页参数的参数,将其传递给数据库, ,然后将 aobject 返回给客户端。PageSlicePageablePage
分页必须由底层持久性层支持,以便 为任何查询提供分页答案。这就是为什么分页和页面类源自 Spring 数据模块,而不是像人们可能的那样。 可疑,来自Spring Web模块。
在启用了自动配置(这是默认设置)的 Spring 启动应用程序中,我们没有 做任何事情,因为它将加载默认情况下,这 包括加载必要 Bean 的注释。SpringDataWebAutoConfiguration@EnableSpringDataWebSupport
在没有 Spring Boot 的普通 Spring 应用程序中,我们必须自己在类上使用:@EnableSpringDataWebSupport@Configuration
- @Configuration
- @EnableSpringDataWebSupport
- class PaginationConfiguration {
- }
如果我们在 Web 控制器方法中使用或参数而没有 激活了 Spring 数据 Web 支持,我们将得到如下异常:PageableSort
- java.lang.NoSuchMethodException: org.springframework.data.domain.Pageable.
() - java.lang.NoSuchMethodException: org.springframework.data.domain.Sort.
()
这些异常意味着 Spring 尝试创建主动脉实例 并且失败,因为它们没有默认构造函数。PageableSort
Spring Data Web 支持修复了这个问题,因为它将PageableHandlerMethodArgumentResolver 和 SortHandlerMethodArgumentResolverbean 添加到 应用程序上下文,负责查找 Web 控制器方法参数 类型并且用页面、大小和排序查询参数的值填充它们。PageableSort
Pageable启用 Spring 数据网络支持后,我们可以简单地使用 aas 作为输入参数 到 Web 控制器方法并将 Aobject 返回给客户端:PageablePage
- @RestController
- @RequiredArgsConstructor
- class PagedController {
-
- private final MovieCharacterRepository characterRepository;
-
- @GetMapping(path = "/characters/page")
- Page
loadCharactersPage(Pageable pageable) { - return characterRepository.findAllPage(pageable);
- }
-
- }
集成测试显示,查询参数,和现在已计算 并“注入”到我们的 Web 控制器方法的参数中:pagesizesortPageable
- @WebMvcTest(controllers = PagedController.class)
- class PagedControllerTest {
-
- @MockBean
- private MovieCharacterRepository characterRepository;
-
- @Autowired
- private MockMvc mockMvc;
-
- @Test
- void evaluatesPageableParameter() throws Exception {
-
- mockMvc.perform(get("/characters/page")
- .param("page", "5")
- .param("size", "10")
- .param("sort", "id,desc") // <-- no space after comma! .param("sort", "name,asc")) // <-- no space after comma! .andExpect(status().isOk());
-
- ArgumentCaptor
pageableCaptor = - ArgumentCaptor.forClass(Pageable.class);
- verify(characterRepository).findAllPage(pageableCaptor.capture());
- PageRequest pageable = (PageRequest) pageableCaptor.getValue();
-
- assertThat(pageable).hasPageNumber(5);
- assertThat(pageable).hasPageSize(10);
- assertThat(pageable).hasSort("name", Sort.Direction.ASC);
- assertThat(pageable).hasSort("id", Sort.Direction.DESC);
- }
- }
测试捕获传递到存储库方法的参数并验证 它具有查询参数定义的属性。Pageable
请注意,我使用了自定义 AssertJ断言,用于在实例上创建可读断言。Pageable
另请注意,为了按多个字段排序,我们必须提供查询参数 多次。每个字段可以仅包含一个字段名称,假定升序, 或带有顺序的字段名称,用逗号分隔,不带空格。如果有空间 在字段名称和订单之间,不会评估订单。sort
Sort同样,我们可以在 Web 控制器方法中使用独立参数:Sort
- @RestController
- @RequiredArgsConstructor
- class PagedController {
-
- private final MovieCharacterRepository characterRepository;
-
- @GetMapping(path = "/characters/sorted")
- List
loadCharactersSorted(Sort sort) { - return characterRepository.findAllSorted(sort);
- }
- }
当然,对象仅使用查询参数的值填充, 如此测试所示:Sortsort
- @WebMvcTest(controllers = PagedController.class)
- class PagedControllerTest {
-
- @MockBean
- private MovieCharacterRepository characterRepository;
-
- @Autowired
- private MockMvc mockMvc;
-
- @Test
- void evaluatesSortParameter() throws Exception {
-
- mockMvc.perform(get("/characters/sorted")
- .param("sort", "id,desc") // <-- no space after comma!!! .param("sort", "name,asc")) // <-- no space after comma!!! .andExpect(status().isOk());
-
- ArgumentCaptor
sortCaptor = ArgumentCaptor.forClass(Sort.class); - verify(characterRepository).findAllSorted(sortCaptor.capture());
- Sort sort = sortCaptor.getValue();
-
- assertThat(sort).hasSort("name", Sort.Direction.ASC);
- assertThat(sort).hasSort("id", Sort.Direction.DESC);
- }
- }
如果我们在调用 带有参数的控制器方法,它将填充 默认值。pagesizesortPageable
Spring Boot 使用@ConfigurationProperties功能来 将以下属性绑定到类型的 Bean:SpringDataWebProperties
- spring.data.web.pageable.size-parameter=size
- spring.data.web.pageable.page-parameter=page
- spring.data.web.pageable.default-page-size=20
- spring.data.web.pageable.one-indexed-parameters=false
- spring.data.web.pageable.max-page-size=2000
- spring.data.web.pageable.prefix=
- spring.data.web.pageable.qualifier-delimiter=_
以上值是默认值。 其中一些属性不是不言自明的,因此以下是它们的作用:
size-parametersizepage-parameterpagedefault-page-sizesizeone-indexed-parameterspagemax-page-sizesizeprefixpagesizesort该物业是一个非常特殊的情况。我们可以在 aMethod 参数,用于为分页查询参数提供本地前缀:qualifier-delimiter@QualifierPageable
- @RestController
- class PagedController {
-
- @GetMapping(path = "/characters/qualifier")
- Page
loadCharactersPageWithQualifier( - @Qualifier("my") Pageable pageable) {
- ...
- }
-
- }
这与上面的属性具有类似的效果,但它也适用于参数。用于分隔前缀 参数名称。在上面的示例中,仅计算查询参数。prefixsortqualifier-delimitermy_pagemy_sizemy_sort
spring.data.web.*属性未评估?
如果对上述配置属性的更改不起作用,则 bean 可能未加载到应用程序上下文中。SpringDataWebProperties
其中一个原因可能是您已经激活了分页支持。这将覆盖, 其中创造了沂豆。仅在普通弹簧应用中使用。@EnableSpringDataWebSupportSpringDataWebAutoConfigurationSpringDataWebProperties@EnableSpringDataWebSupport
有时我们可能只想为单个控制器方法定义默认分页参数。 对于这种情况,我们可以使用theandannotations:@PagableDefault@SortDefault
- @RestController
- class PagedController {
-
- @GetMapping(path = "/characters/page")
- Page
loadCharactersPage( - @PageableDefault(page = 0, size = 20)
- @SortDefault.SortDefaults({
- @SortDefault(sort = "name", direction = Sort.Direction.DESC),
- @SortDefault(sort = "id", direction = Sort.Direction.ASC)
- }) Pageable pageable) {
- ...
- }
-
- }
如果未给出查询参数,则对象现在将填充 批注中定义的默认值。Pageable
请注意,theannotation 也有属性,但如果我们想要 要定义多个字段以按不同方向排序,我们必须使用。@PageableDefaultsort@SortDefault
由于本文中描述的分页功能来自Spring Data, Spring Data完全支持分页也就不足为奇了。然而,这种支持是 解释得非常快,因为我们只需要添加正确的参数并返回值 到我们的存储库界面。
我们可以简单地将主动脉实例传递到任何 Spring 数据存储库方法中:PageableSort
- interface MovieCharacterRepository
- extends CrudRepository
{ -
- List
findByMovie(String movieName, Pageable pageable); -
- @Query("select c from MovieCharacter c where c.movie = :movie")
- List
findByMovieCustom( - @Param("movie") String movieName, Pageable pageable);
-
- @Query("select c from MovieCharacter c where c.movie = :movie")
- List
findByMovieSorted( - @Param("movie") String movieName, Sort sort);
-
- }
即使Spring Data提供了一个PagingAndSortingRepository,我们也不必 使用它来获取分页支持。它只是 提供两种方便的方法,一种使用 a,另一种使用 a参数。findAllSortPageable
如果我们想将页面信息返回给客户端而不是简单的列表, 我们只是让我们的存储库方法简单地返回 aor a:SlicePage
- interface MovieCharacterRepository
- extends CrudRepository
{ -
- Page
findByMovie(String movieName, Pageable pageable); -
- @Query("select c from MovieCharacter c where c.movie = :movie")
- Slice
findByMovieCustom( - @Param("movie") String movieName, Pageable pageable);
-
- }
每个返回主动脉的方法都必须只有一个参数,否则 Spring Data将在启动时投诉,但有例外。SlicePagePageable
Spring 数据 Web 支持使普通 Spring 应用程序和 Spring 中的分页变得容易。 启动应用程序。这是激活它然后使用正确的输入和输出的问题 控制器和存储库方法中的参数。
借助 Spring Boot 的配置属性,我们可以对默认值进行细粒度控制。 和参数名称。
不过,有一些潜在的问题,其中一些我在上面的文字中已经描述了,所以 你不必绊倒它们。
如果您缺少有关与 Spring 进行寻呼的任何内容,请在此 教程,在评论中让我知道。
您可以在github 上找到本文中使用的示例代码。