当前位置: 首页 > news >正文

Java分页查询方式总结

文章目录

    • 分页查询核心思路
    • 常见分页实现方式
      • MyBatis
      • MyBatis-Plus

分页查询核心思路

分页的本质是限制查询结果的条数+跳过指定行数,并查询总记录数(用于计算总页数)。核心参数:

  • pageNum:当前页码(从 1 开始)
  • pageSize:每页显示条数
  • 起始行计算:startRow = (pageNum - 1) * pageSize
  • 总页数计算:totalPages = (totalCount + pageSize - 1) / pageSize(向上取整)

常见分页实现方式

MyBatis

1、mapper手动拼SQL

Mapper 接口:

import org.apache.ibatis.annotations.Param; import java.util.List; public interface UserMapper { // 查询分页数据 List<User> selectUserByPage(@Param("startRow") int startRow, @Param("pageSize") int pageSize); // 查询总条数 int selectUserTotalCount(); }

Mapper.xml:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.UserMapper"> <!-- 分页查询数据 --> <select id="selectUserByPage" resultType="com.example.entity.User"> SELECT id, name, age FROM user LIMIT #{startRow}, #{pageSize} </select> <!-- 查询总条数 --> <select id="selectUserTotalCount" resultType="int"> SELECT COUNT(*) FROM user </select> </mapper>

2、PageHelper 插件

需要在 pom.xml 中添加 PageHelper 依赖

<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.7</version> </dependency>

在调用时添加如下代码

// 开启分页(只对紧接着的第一个查询生效) PageHelper.startPage(pageNum, pageSize);

MyBatis-Plus

1、使用内置分页插件 PaginationInnerInterceptor

Page<Forlan>page=newPage<>(pageNum,pageSize);QueryWrapper<Forlan>queryWrapper=newQueryWrapper<>();queryWrapper.eq("id",id);mapper.selectPage(page,queryWrapper);service.lambdaQuery().page(newPage<>(pageNum,pageSize))

2、基于偏移量的分页(升级写法,自己控制结束)

核心参数:

  1. LIMIT N
    表示“只返回最多 N 条记录”。
    通常用来控制每页显示的数据条数。
  2. OFFSET M
    表示“跳过前 M 条记录”,从第 M+1 条开始取数据。
    用于定位到当前请求的页码起始位置。
    核心:service.query().last("LIMIT " + batchSize + " OFFSET " + offset).list();
finalintbatchSize=1000;List<Forlan>allResults=newArrayList<>();intoffset=0;booleanhasMoreResults=true;while(hasMoreResults){try{List<Forlan>batchResults=service.query().last("LIMIT "+batchSize+" OFFSET "+offset).list();if(batchResults==null||batchResults.isEmpty()){hasMoreResults=false;}else{allResults.addAll(batchResults);offset+=batchResults.size();}}catch(Exceptione){log.info("Error querying batch: ",e);hasMoreResults=false;}}returnallResults;

上面的写法,存在问题:随着 offset 增大,性能下降严重,对大数据量场景不友好,适合深度翻页,比如:

-- 查询第10001页,每页10条数据 SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 100000;

这条SQL的执行逻辑并非直接定位到第100,001条记录。MySQL的实际处理过程是:从存储引擎中读取满足条件的前 100010 (OFFSET + LIMIT) 条记录,在服务层(Server Layer)对这些记录进行排序,抛弃前面的 100000 条记录,返回最终的 10 条记录。

所以,OFFSET 值越大,MySQL需要扫描、加载并最终抛弃的行数就越多,这导致了巨大的I/O和CPU资源浪费,是性能下降的直接原因。

1)延迟关联:优化后的写法

核心思想:先通过覆盖索引快速定位到目标页的主键ID,然后再关联原表获取完整的行数据,从而减少对主表数据的扫描。

LonglastId=0L;finalintbatchSize=1000;List<Forlan>allResults=newArrayList<>();booleanhasMoreResults=true;while(hasMoreResults){try{List<Forlan>batchResults=service.query().gt("id",lastId).last("LIMIT "+batchSize).list();if(batchResults==null||batchResults.isEmpty()){hasMoreResults=false;}else{allResults.addAll(batchResults);lastId=batchResults.get(batchResults.size()-1).getId();}}catch(Exceptione){log.info("Error querying batch: ",e);hasMoreResults=false;}}returnallResults;

存在问题,如果扫描的最小id在几千万,这时候首次查询也是非常耗费时间的,进一步优化的写法如下:

finalintbatchSize=1000;List<Forlan>allResults=newArrayList<>();booleanhasMoreResults=true;Forlanforlan=service.query().select("min(id) id").one();if(forlan==null){returnallResults;}LonglastId=forlan.getId()-1;while(hasMoreResults){try{List<Forlan>batchResults=service.query().gt("id",lastId).last("LIMIT "+batchSize).list();if(batchResults==null||batchResults.isEmpty()){hasMoreResults=false;}else{allResults.addAll(batchResults);lastId=batchResults.get(batchResults.size()-1).getId();}}catch(Exceptione){log.info("Error querying batch: ",e);hasMoreResults=false;}}returnallResults;

2)书签法

是目前性能最优的方案。它摒弃了OFFSET,通过上一页最后一条记录的唯一键值来定位下一页的起始位置,但要求主键或查询条件连续

假设我们按自增id排序,上一页返回的最后一条记录id为100000。不使用OFFSET,而是利用上一页的id进行定位

SELECT * FROM products WHERE id > 100000 ORDER BY id ASC LIMIT 10;

优点:查询性能恒定,不受分页深度影响,速度极快。

http://www.jsqmd.com/news/194592/

相关文章:

  • 喂饭级教程(番外篇)—— 在 K8s 上部署 Dify
  • 昆仑通态与东元N310变频器通讯实战之旅
  • 【教程】如何在电脑上安装dify
  • 研究生必备:7款AI写论文工具,半天搞定全文告别熬夜赶稿 - 麟书学长
  • alma 下 设置 nvidia nim 之 z-ai/glm4.7 或 minimax-m2.1
  • 辅酶Q10该怎么选?2026十大辅酶Q10品牌排行,第一名品质有目共睹 - 博客万
  • 毕业季必看!8款AI写论文神器实测:文理医工全覆盖,30分钟搞定初稿!
  • 中老年人别再乱买养生产品!这款维生素 B 族复配叶酸降同型半胱氨酸,改善认知超靠谱 - 博客万
  • Android 16安兔兔分辨率作假显示(非修改TextView方案)
  • 精益生产的两大支柱到底是什么?一文帮你搞清楚
  • 基于10部权威医疗电子书的医疗知识图谱构建数据集:包含18,297个结构化标记、37,381个医学实体、5,770个交叉引用关系和974个表格结构,支持疾病-药物关系抽取、临床决策系统开发
  • 360度VR全景设备技术测评与行业应用分析
  • 上汽大众2025年销量突破百万大关,终端销售106万辆
  • 实战解析:京东关键词搜索 item_search_pro —— 按关键字搜索商品
  • 偷懒也高效:帮你准备好的提示词复制范本(附场景)
  • 2026最新延吉韩式炸鸡本土品牌top5推荐!延吉本地特色,延边大学等地优质餐饮店及加盟连锁品牌深度解析/选择指南,脆皮多汁引爆味蕾狂欢 韩式炸鸡品牌推荐 - 全局中转站
  • vue3 实时通讯 SSE
  • A-68 语音处理模组 —— 波束成型 + 双麦降噪,全场景音频交互升级方案
  • LangGraph 是什么?一文秒懂且通俗易懂!
  • HISTCMD 介绍
  • typora快速下载(简单易学)
  • Redis入门篇001_Redis简介与特性
  • mineru离线环境解析文档报“Connection to paddleocr.bj.bcebos.com timed out.”
  • C++ 入门第一课:命名空间、IO 流、缺省参数与函数重载全解析 - 实践
  • telnet远程登陆与管理
  • d3d9.dll文件损坏丢失找不到 打不开软件问题 免费下载方法
  • 25年总结 | 26年规划
  • Java 大视界 -- Java 大数据在智能医疗远程康复监测与个性化康复方案制定中的应用
  • 震惊!这家酶制剂工厂竟让同行都慌了
  • 千万别错过!这5家酶制剂厂让生产效益翻倍