网页数据一页一页加载,然后展示,然后加载在展示,或是无限下来虽然感觉不到,当也确确实实的分页了,spring 全家桶做做 Web 开发,分页是少不了的功能,有的话当然是能用就用。

类的限定名称

  • org.springframework.data.domain.Page
  • org.springframework.data.domain.PageImpl
  • org.springframework.data.domain.Pageable
  • org.springframework.data.domain.Sort
  • org.springframework.data.domain.Slice
  • org.springframework.data.domain.SliceImpl
  • org.springframework.data.web.SortDefault
  • org.springframework.data.web.PageableDefault
  • org.springframework.data.repository.Repository

输入接口 Pageable

Pageable 作为输入参数,包括了页码、页面大小、排序规则信息,这个是一个接口,包含在Spring Data Commons包里面,如果开发的软件引入了Spring Data中随便哪一个组件都会带入这个接口。

interface Pageable {
    val pageNumber: Int
    val pageSize: Int
    val sort: Sort?
}

Sort 作为另外一个接口包括了多个排序字段、升序降序信息。

Pageable是接口类型,不能实例化,通常用PageRequest新建:

    val pageable = PageRequest.of(
        0, 5, Sort.by(
            Sort.Order.asc("name"),
            Sort.Order.desc("id")
        )
    )

输出接口 Page 和 Slice

Page 和 Slice 和前面的 Pageable 都是接口,非常的符合 Java 面向接口编程的味道,Page 继承了 Slice,通常这两个是在输入参数里面存在 Pageable 的时候,作为返回值返回。

Slice 结构

interface Slice<T> : Streamable<T> {
    val number: Int
    val size: Int
    val numberOfElements: Int
    val content: List<T>?
    val sort: Sort?
    // 其他方法和字段
}

Page结构

Page 对比 Slice 多出了总页数和总元素数量这两个字段。

interface Page<T> : Slice<T> {
    val totalPages: Int
    val totalElements: Long
    // 其他方法和字段
}

PageImpl 是 Page 接口的常用实现,同样 Slice 接口对应的实现是 SliceImpl。

val ret = PageImpl(listOf(1,2,3,4,5))

在控制层获取分页输入参数

Pageable 是接口,如果使用的是 SpringMVC,直接作为 Controller Handler 参数并不能自动注入,直接调用会出现下面异常:

java.lang.NoSuchMethodException: org.springframework.data.domain.Pageable.<init>()
java.lang.NoSuchMethodException: org.springframework.data.domain.Sort.<init>()

需要使用配置注解 @EnableSpringDataWebSupport 开启相应的功能,使用这个注解会加载 PageableHandlerMethodArgumentResolver 和 SortHandlerMethodArgumentResolver 这两个解析器,从请求参数里面解析 Pageable 参数,并调用自带实现。如果是 SpringBoot,这一切都已经做好了,直接使用即可。

@Configuration
@EnableSpringDataWebSupport
class PaginationConfiguration {
}

自定义分页参数

全局分页参数

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-parametersize分页大小变量名称
page-parameterpage分页页面变量名称
default-page-size20默认每页数量
one-indexed-parametersfalse页码从0开始还是从1开始
max-page-size2000每页最大数量
prefixpage和size参数前缀(不包括sort)
qualifier-delimiter_分隔符

如果 one-indexed-parameters 设置为 true,默认页码将从 1 开始,否则从 0 开始。@Qualifier 注解可以给分页输入参数添加一个类似命名空间的东西,qualifier-delimiter 表示分割这个前缀的分隔符。

fun books(@Qualifier("book") p:Pageable){
}

qualifier-delimiter 默认值为 "_" 的情况下,只有 book_size / book_page / book_sort 能给正确读取。

这些参数最终会应用于 PageableHandlerMethodArgumentResolverSupport 这个类。

局部分页参数

@PageableDefault 用于设定页码和大小,@SortDefault 用于设定排序规则,@SortDefault.SortDefaults 组合多个排序规则。

fun books(
    @PageableDefault(page = 0, size = 100)
    @SortDefault.SortDefaults(
        SortDefault(sort = ["name"], direction = Sort.Direction.ASC),
        SortDefault(sort = ["id"], direction = Sort.Direction.DESC)
    )
    p: Pageable
) {
}

DataRepository 分页支持

Spring Data Repository 是 Spring Data 数据对象持久化的抽象层,通过这个逻辑分层可以屏蔽底层数据库差异,常用的驱动都附带 Crud 功能实现。这一部分包括接口 Repository ,和继承的Repository 的 CrudRepository 接口,还有 CrudRepository 在之上的 PagingAndSortingRepository 接口。

将 Pageable 作为方法调用参数传递,系统将会自动处理分页,根据接口方法返回类型是 List / Slice / Page 中的哪一个,如果是 Page 类型会多出一次查询,查询符合要求数据总的数量,其余的将会自动封装返回,Slice 和 Page 对比 List 会附带 Pageable 参数。同样对于 Page / Slice 返回类型,Pageable 参数是必须的,如果缺少会抛出异常。如果不需要分页,只需要排序,单独传递 Sort 对象即可。

interface BookRepository : Repository<Book, Int> {
    fun findByName(name: String, p: Pageable): Page<Book>
}