MySQL中,LIMIT配合OFFSET是实现分页查询最直接、最基本的方法。
核心逻辑非常简单:LIMIT N:表示我要取 N 条数据。OFFSET M:表示我要跳过前 M 条数据。
sql
SELECT * FROM table_a ORDER BY id LIMIT 每页数量 OFFSET (当前页码-1)*每页数量;
举例: 想要查第 10 页的数据(每页 10 条),SQL 就是 LIMIT 10 OFFSET 90
在实际项目开发中,有个专门用于分页查询的插件PageHelper,需要先在pom.xml文件中添加依赖项,并在application.yml文件中添加配置
XML
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
XML
pagehelper:
helper-dialect: mysql # 指定数据库类型
reasonable: true # 启用合理化,比如页码小于1自动查第一页
support-methods-arguments: true # 支持直接传参
比如,在实体类bidding中,有字段id,customer_name, bid_deposit, agency_period,agency_power,package_agreement, contractual_terms, other_content,create_time, update_time,我现在希望它的结果按照创建时间排序并分页展示出来
前端提供当前页码page和每页展示的数据量pageSize,使用这个插件给这两个参数就可以自动完成分页,在查询的时候还是正常查询所有的数据,返回会自动给分页后的链表,PageHelper 是一个Mybatis的分页插件,本质是Mybatis的拦截器 ,它会拦截Mybatis所有的查询SQL执行请求。
PageHelper插件在业务层的使用就是:
java
@Service
@Slf4j
public class BiddingServiceImpl implements BiddingService {
@Autowired
private BiddingMapper bmapper;
@Override
public PageResult<Bidding> pageSelect(Integer page, Integer pageSize) {
PageHelper.startPage(page,pageSize);
List<Bidding> l=bmapper.selectBidding();
Page<Bidding> p=(Page<Bidding>) l;
return new PageResult<>(p.getTotal(),p.getResult());
}
}
XML
<mapper namespace="com.amaranth.mapper.BiddingMapper">
<select id="selectBidding" resultType="com.amaranth.pojo.entity.Bidding">
SELECT id,customer_name, bid_deposit, agency_period, agency_power,
package_agreement, contractual_terms, other_content,
create_time, update_time
FROM users_db.bidding_db ORDER BY create_time DESC
</select>
<select id="getTotal" resultType="java.lang.Long">
SELECT COUNT(*) FROM users_db.bidding_db
</select>
</mapper>
而这里之所以可以将List强转为Page,是因为Page在源码中是继承的ArrayList,很明显,ArrayList也是List的一个实现类,所以 Page 对象 可以向上转型 赋值给 List 类型的变量,这是完全符合 Java 语法的。Page里面封装了:总条数total、当前页pageNum、页大小pageSize、总页数pages等所有分页属性;源码里面还有个getResult()方法就是直接返回Page对象自己,getTotal()的值这个值就是 PageHelper自动帮忙执行的那条 count(0) SQL 的查询结果
前端需要提供当前页码page和每页展示的数据量pageSize,通过前端传递的page和pageSize,PageHelper就可以拦截所有的SQL请求,后端返回的是展示的这一页的链表和数据总数total,并封装在一个PageResult类中。
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult<T> {
private Long total;
private List<T> rows;
}
这个rows链表可以通过selectBidding获取,而总数通过getTotal返回。由于数据量可能比较大,id用的是Long类型,所以计算当前记录的行数时要注意数据类型转换。在业务层将需要返回的当页记录链表和总记录数封装在一个PageResult类中,返回给controller层
如果没有传递page和pageSize时给默认值,在controller层接收参数时添加@RequestParam注解这里当前页码page设置为1,每页展示条数pageSize设置为5,这个@RequestParam注解的默认值也只能在controller层给定。而业务层返回来的PageResult作为Result中的Data返回给前端。
XML
@RestController
@RequestMapping("/v1/bidding")
@Slf4j
public class BiddingController {
@Autowired
private BiddingService bservice;
@GetMapping("/pageSelect")
public Result selectBidding(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "5") Integer pageSize){
log.info("请求参数:page={},pageSize={}",page,pageSize);
try {
// 调用业务层查询方法
PageResult<Bidding> presult = bservice.pageSelect(page,pageSize);
// 查询成功:返回【成功提示】,有数据
return Result.success(presult);
} catch (Exception e) {
// 查询失败:捕获所有异常,返回【失败提示】,提示信息为异常原因
return Result.fail("获取招投标信息失败:" + e.getMessage());
}
}
}
在IDEA的http.test中测试即可看到code,msg,data

尽管使用插件看起来很方便,但它的性能比较差,PageHelper 默认用法低效执行逻辑SQL查询,PageHelper的核心使用方式是:先执行全量查询,再在内存中做分页,PageHelper 会让 Mybatis 执行 SELECT 字段 FROM 表 → 查询表里的所有数据,把全表数据全部加载到 Java 内存中,然后再做拦截和筛选,这是它效率低的根本原因。可以在apifox上看到响应时间:

这里是花费了426ms。
为了提高性能,自己手动实现分页查询在调用mapper层的时候,就应该传入两个参数currentRow和pageSize分别表示当前记录行数和每页记录数,有了这两个参数就可以实现分段提取数据,而不是像PageHelper那样查询全部然后筛选
XML
<mapper namespace="com.amaranth.mapper.BiddingMapper">
<select id="selectBidding" resultType="com.amaranth.pojo.entity.Bidding">
SELECT id,customer_name, bid_deposit, agency_period, agency_power,
package_agreement, contractual_terms, other_content,
create_time, update_time
FROM users_db.bidding_db ORDER BY create_time DESC
LIMIT #{currentRow}, #{pageSize}
</select>
<select id="getTotal" resultType="java.lang.Long">
SELECT COUNT(*) FROM users_db.bidding_db
</select>
</mapper>
XML
@Mapper
public interface BiddingMapper {
List<Bidding> selectBidding(@Param("currentRow") Long currentRow,
@Param("pageSize") Integer pageSize);
Long getTotal();
}
XML
@Service
@Slf4j
public class BiddingServiceImpl implements BiddingService {
@Autowired
private BiddingMapper bmapper;
@Override
public PageResult<Bidding> pageSelect(Integer page, Integer pageSize) {
Long currentRow=(long) (page-1)*pageSize;
PageResult<Bidding> presult=new PageResult<>();
List<Bidding> list=bmapper.selectBidding(currentRow,pageSize);
log.info("list={}",list);
Long total=bmapper.getTotal();
log.info("total={}",total);
presult.setRows(list);
presult.setTotal(total);
return presult;
}
}
业务层在计算当前记录行数时,由于只能接收到controller层传过来的当前页面page和每页记录数pageSize,那么当前行数就是(page-1)*pageSize,需要注意类型转换。
那么就看这次的响应时间:

这两条 SQL 都是纯原生、无任何额外开销的查询,数据库能走索引、精准命中,执行效率拉满,所以耗时只有 34ms,这是最推荐的分页写法。