文档站点
Skip to content

分页 ​

Laravel 用 paginate() / paginate()->links() 返回带分页信息的集合;Spring Boot 的分页分两种风格:JPA 的 Pageable(对应 Eloquent 分页,推荐)和 手写 SQL 的 PageHelper / 手动 LIMIT(对应手动分页)。

方案一:Spring Data JPA 的 Pageable(最推荐,类 Eloquent) ​

Repository 方法接受 Pageable ​

java
public interface PostRepository extends JpaRepository<Post, Long> {

    // 分页查询:方法签名里加 Pageable
    Page<Post> findByStatusOrderByCreatedAtDesc(int status, Pageable pageable);

    Page<Post> findByUserId(Long userId, Pageable pageable);
}

Controller 接收分页参数 ​

java
@GetMapping("/posts")
public Page<Post> index(
        @RequestParam(defaultValue = "1") int page,       // 第几页,从 1 开始
        @RequestParam(defaultValue = "15") int size) {    // 每页条数
    return postRepository.findByStatusOrderByCreatedAtDesc(
            1, PageRequest.of(page - 1, size));           // JPA 的 page 从 0 开始!
}

返回的分页信息 ​

json
{
  "content": [...],
  "pageable": {...},
  "totalElements": 100,
  "totalPages": 7,
  "number": 0,
  "size": 15,
  "first": true,
  "last": false,
  "empty": false
}

对照 Laravel

Laravel paginate()Spring Data JPA Page
$posts->total()page.getTotalElements()
$posts->lastPage()page.getTotalPages()
$posts->currentPage()page.getNumber() + 1
$posts->items()page.getContent()
$posts->hasMorePages()page.hasNext()
参数 page / per_pagePageRequest.of(page-1, size)
页码从 1 开始页码从 0 开始(容易踩坑!)

JPA 页码从 0 开始

PageRequest.of(0, 15) 是「第 1 页」。前端传第 1 页时,要 PageRequest.of(page - 1, size) 转一下。这是 JPA 和 Laravel 分页最大的习惯差异。

简化:全局「page + size」参数绑定 ​

Pageable 可以作为控制器方法参数直接注入,Spring 自动从 ?page=&size= 读取:

java
@GetMapping("/posts")
public Page<Post> index(Pageable pageable) {
    return postRepository.findAll(pageable);
}

请求 ?page=0&size=15&sort=createdAt,desc,Spring 连排序都帮你解析。

TIP

Pageable 参数的默认配置:

yaml
spring:
  data:
    web:
      pageable:
        default-page-size: 15
        max-page-size: 100

方案二:JdbcTemplate 手动 LIMIT ​

不用 ORM 时,自己拼 SQL 分页:

java
@Repository
public class PostDao {

    public Page<Post> page(int page, int size) {
        int offset = (page - 1) * size;

        List<Post> rows = jdbc.query(
                "SELECT * FROM posts ORDER BY id DESC LIMIT ? OFFSET ?",
                new PostRowMapper(), size, offset);

        Integer total = jdbc.queryForObject("SELECT COUNT(*) FROM posts", Integer.class);

        return new PageImpl<>(rows, PageRequest.of(page - 1, size), total);
    }
}

PageImpl 是 Spring Data 提供的现成实现,直接用它包装返回统一 Page 结构。

方案三:MyBatis 的 PageHelper(国内主流) ​

xml
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
java
@Service
public class PostService {

    public PageInfo<Post> page(int page, int size) {
        // 下一行查询自动分页(PageHelper 用线程本地变量拦截 SQL)
        PageHelper.startPage(page, size);
        List<Post> list = postMapper.selectAll();     // 普通 SQL 不用写 LIMIT
        return new PageInfo<>(list);                  // 包装分页信息
    }
}

PageInfo 里带 total、pages、pageNum 等字段,无需手动数。

PageHelper 原理

PageHelper.startPage(page, size) 之后的下一条查询会被 AOP 拦截,自动追加 LIMIT 并执行一条 COUNT。注意:必须紧跟 startPage 后的第一条查询才生效,中间别插其他 SQL。这是国内 MyBatis 生态最常用的分页方案。

实践建议 ​

场景方案
用 JPAPageable(最省心)
用 JdbcTemplate手动 LIMIT + PageImpl
用 MyBatisPageHelper
超大数据量改用「游标分页 / 基于 ID 的 keyset 分页」,别用 LIMIT offset

深分页性能问题

LIMIT 100000, 20 这种大 offset 会越来越慢。数据量大时用「上次最大 ID」:

sql
SELECT * FROM posts WHERE id < ?last_id ORDER BY id DESC LIMIT 20

这就是 keyset 分页,Laravel 下同样适用。

面向 PHP / Laravel 开发者的 Spring Boot 中文文档