分页
Laravel 用
paginate()/paginate()->links()返回带分页信息的集合;Spring Boot 的分页分两种风格:JPA 的Pageable(对应 Eloquent 分页,推荐)和 手写 SQL 的PageHelper/ 手动 LIMIT(对应手动分页)。
方案一:Spring Data JPA 的 Pageable(最推荐,类 Eloquent)
Repository 方法接受 Pageable
public interface PostRepository extends JpaRepository<Post, Long> {
// 分页查询:方法签名里加 Pageable
Page<Post> findByStatusOrderByCreatedAtDesc(int status, Pageable pageable);
Page<Post> findByUserId(Long userId, Pageable pageable);
}Controller 接收分页参数
@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 开始!
}返回的分页信息
{
"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_page | PageRequest.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= 读取:
@GetMapping("/posts")
public Page<Post> index(Pageable pageable) {
return postRepository.findAll(pageable);
}请求 ?page=0&size=15&sort=createdAt,desc,Spring 连排序都帮你解析。
TIP
Pageable 参数的默认配置:
spring:
data:
web:
pageable:
default-page-size: 15
max-page-size: 100方案二:JdbcTemplate 手动 LIMIT
不用 ORM 时,自己拼 SQL 分页:
@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(国内主流)
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>@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 生态最常用的分页方案。
实践建议
| 场景 | 方案 |
|---|---|
| 用 JPA | Pageable(最省心) |
| 用 JdbcTemplate | 手动 LIMIT + PageImpl |
| 用 MyBatis | PageHelper |
| 超大数据量 | 改用「游标分页 / 基于 ID 的 keyset 分页」,别用 LIMIT offset |
深分页性能问题
LIMIT 100000, 20 这种大 offset 会越来越慢。数据量大时用「上次最大 ID」:
SELECT * FROM posts WHERE id < ?last_id ORDER BY id DESC LIMIT 20这就是 keyset 分页,Laravel 下同样适用。