Spring Boot分页查询案例

wen java案例 3

Spring Boot分页查询实战:从基础到性能优化的完整指南

📑 目录导读

  1. 为什么分页查询是后端开发的“必修课”
  2. 基础篇:基于Spring Data JPA的极简分页实现
  3. 进阶篇:MyBatis-Plus分页插件的高效用法
  4. 性能陷阱:大偏移量分页的隐患与解决方案
  5. 实战问答:面试官最爱问的5个分页问题
  6. 总结与最佳实践清单

为什么分页查询是后端开发的“必修课”

在任何一个真实业务系统中,数据量超过几百条后,一次性返回全部数据都会导致内存溢出网络带宽浪费前端渲染卡死,分页查询(Pagination)正是解决这一问题的核心手段,Spring Boot作为主流Java微服务框架,天然支持多种分页方案。

Spring Boot分页查询案例

核心痛点:不合理的分页实现会导致数据库负载飙升(例如OFFSET 1000000),或者产生脏读数据,掌握正确姿势,是高级开发与初级开发的分水岭。


基础篇:基于Spring Data JPA的极简分页实现

Spring Data JPA提供了开箱即用的分页支持,只需三步:

1 定义Repository接口

public interface UserRepository extends JpaRepository<User, Long> {
    // 无需写实现,继承自带方法
}
// 或者自定义查询分页
@Query("SELECT u FROM User u WHERE u.age > :age")
Page<User> findByAgeGreaterThan(@Param("age") int age, Pageable pageable);

2 Service层调用

public Page<User> getUsers(int page, int size) {
    Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
    return userRepository.findAll(pageable);
}

3 Controller层响应

@GetMapping("/users")
public Page<User> listUsers(@RequestParam(defaultValue = "0") int page,
                            @RequestParam(defaultValue = "10") int size) {
    return userService.getUsers(page, size);
}

注意PageRequest.of(page, size) 中page从0开始,返回的Page对象自动包含总数、总页数等元数据。


进阶篇:MyBatis-Plus分页插件的高效用法

国内项目更常用MyBatis-Plus,其分页插件性能优于JPA原生方式:

1 配置分页拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

2 使用分页方法

// Service Impl
public Page<User> pageUsers(int current, int size) {
    Page<User> page = new Page<>(current, size);
    return userMapper.selectPage(page, new QueryWrapper<User>().gt("age", 18));
}

优势:自动生成COUNT查询,并优化了JOIN场景下的分页SQL。


⚠️ 性能陷阱:大偏移量分页的隐患与解决方案

page * size超过几万时,传统LIMIT/OFFSET会导致全表扫描,以下是三种主流方案:

方案 适用场景 缺陷
延迟关联 大数据量 需拆两条SQL
游标分页 实时数据流 不能跳页
子查询优化 中小数据 嵌套导致复杂

1 延迟关联示例(MySQL)

-- 性能差(OFFSET大)
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;
-- 性能优(先查ID再关联)
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t
ON o.id = t.id;

2 游标分页(Seek Method)

// 要求排序字段唯一且连续
@Query("SELECT u FROM User u WHERE u.id < :lastId ORDER BY u.id DESC")
Page<User> findByCursor(@Param("lastId") Long lastId, Pageable pageable);

经验之谈:后端接口应同时支持“页码模式”和“游标模式”,根据业务场景切换。


实战问答:面试官最爱问的5个分页问题

Q1:Spring Data JPA的PageSlice有什么区别? A:Page包含总数(需要COUNT查询),Slice只判断是否有下一页(更轻量),对于实时性高的列表,用Slice性能更好。

Q2:分页时如何避免“重复数据”或“数据缺失”? A:必须要有稳定的排序字段(如主键ID),如果使用createTime等非唯一字段,需追加id作为次级排序,否则当新数据插入时,分页边界会漂移。

Q3:分页查询时,COUNT查询非常慢怎么办? A:对于大表,不要依赖MySQL COUNT(*),可以:

  • 使用Redis缓存总数(定时更新)
  • 采用近似计数(如查information_schema)
  • 改用游标分页彻底规避COUNT

Q4:MyBatis-Plus分页和JPA分页可以混用吗? A:技术栈允许,但强烈建议统一规范,混用会导致代码维护成本剧增,且不同分页逻辑的排序规则不一致容易出Bug。

Q5:分页参数需要校验吗? A:必须校验!size不能超过100(防爬虫),page不能为负数,建议用@Min@Max注解,或者在Controller层统一处理。


总结与最佳实践清单

实践点 建议
默认分页大小 10-20条
最大分页限制 100条
排序字段 必须包含唯一键
大偏移量 超过1000页改用游标
日志记录 打印SQL和参数,便于排查慢查询
接口幂等 分页接口应支持缓存(如ETag)

最后一道防线:生产环境务必开启慢SQL监控(如Druid、P6Spy),当某页查询超过300ms立即告警。


通过以上实战拆解,你已从“会调API”升级到“懂分页原理”,真正的优化往往不在代码,而在对数据分布和索引的深刻理解,希望这篇文章能成为你简历上“精通分页查询”的底气。

(文章原创,创作过程中参考了Spring官方文档、MyBatis-Plus官方wiki及国内外技术社区讨论,并结合多年电商高并发场景实战经验整理)

抱歉,评论功能暂时关闭!