以下是 原生 SQL 分页、PageHelper 的 Page
对象和 PageInfo
对象 三种分页查询方式的详细实现讲解,包含代码示例和对比分析。
一、原生 SQL 分页(不依赖 PageHelper)
1. 实现步骤
- SQL 分页语法:手动编写
LIMIT offset, pageSize
。 - 总记录数查询:单独编写
COUNT(*)
SQL。 - 参数计算:手动计算
offset = (pageNum - 1) * pageSize
。
2. 代码示例
// 1. Mapper 接口
public interface EmpMapper {
// 分页查询数据
@Select("SELECT * FROM emp WHERE dept_id = #{deptId} ORDER BY id DESC LIMIT #{offset}, #{pageSize}")
List<Emp> selectByPage(@Param("deptId") Long deptId, @Param("offset") int offset, @Param("pageSize") int pageSize);
// 查询总记录数
@Select("SELECT COUNT(*) FROM emp WHERE dept_id = #{deptId}")
long countTotal(@Param("deptId") Long deptId);
}
// 2. Service 层
public PageResult<Emp> queryEmpByPage(EmpQueryDTO dto) {
int pageNum = dto.getPageNum();
int pageSize = dto.getPageSize();
int offset = (pageNum - 1) * pageSize;
// 查询当前页数据
List<Emp> data = empMapper.selectByPage(dto.getDeptId(), offset, pageSize);
// 查询总记录数
long total = empMapper.countTotal(dto.getDeptId());
return new PageResult<>(total, data);
}
// 3. 分页结果类
public class PageResult<T> {
private long total;
private List<T> list;
// 其他字段...
}
3. 优缺点
- 优点:无第三方依赖,灵活控制 SQL。
- 缺点:需手动处理分页参数和总条数查询,代码冗余。
二、PageHelper 的 Page
对象
1. 实现步骤
- 启动分页:调用
PageHelper.startPage(pageNum, pageSize)
。 - 查询数据:直接执行查询,返回
Page<T>
(本质是ArrayList
的子类)。 - 获取分页数据:通过
Page<T>
对象直接读取总记录数、当前页数据等。
2. 代码示例
// 1. Mapper 接口(无需分页参数)
public interface EmpMapper {
@Select("SELECT * FROM emp WHERE dept_id = #{deptId} ORDER BY id DESC")
List<Emp> selectByCondition(@Param("deptId") Long deptId);
}
// 2. Service 层
public Page<Emp> queryEmpByPage(EmpQueryDTO dto) {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<Emp> list = empMapper.selectByCondition(dto.getDeptId());
return (Page<Emp>) list; // 强制转换为 Page 对象
}
// 3. Controller 层
@GetMapping("/emps")
public PageResult<Emp> listEmps(EmpQueryDTO dto) {
Page<Emp> page = empService.queryEmpByPage(dto);
return new PageResult<>(page.getTotal(), page.getResult());
}
3. 关键点
- 强制转换:
Page<T>
是ArrayList
的子类,可以直接转换。 - 分页元数据:
Page<T>
包含total
、pageNum
、pageSize
等字段。
4. 优缺点
- 优点:简化分页参数处理,自动生成
COUNT
查询。 - 缺点:需强制类型转换,返回的
Page
对象包含数据库方言细节。
三、PageHelper 的 PageInfo
对象
1. 实现步骤
- 启动分页:
PageHelper.startPage(pageNum, pageSize)
。 - 查询数据:返回普通
List<T>
。 - 封装结果:用
PageInfo
包裹List<T>
,自动计算总页数、是否有下一页等。
2. 代码示例
// 1. Service 层
public PageInfo<Emp> queryEmpByPage(EmpQueryDTO dto) {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<Emp> list = empMapper.selectByCondition(dto.getDeptId());
return new PageInfo<>(list);
}
// 2. Controller 层
@GetMapping("/emps")
public PageInfo<Emp> listEmps(EmpQueryDTO dto) {
return empService.queryEmpByPage(dto);
}
3. PageInfo
核心字段
public class PageInfo<T> {
private int pageNum; // 当前页码
private int pageSize; // 每页数量
private long total; // 总记录数
private int pages; // 总页数
private List<T> list; // 当前页数据
private boolean hasNextPage; // 是否有下一页
// 其他字段...
}
4. 优缺点
- 优点:开箱即用,封装完整分页信息,无需手动转换。
- 缺点:返回字段固定,若需自定义字段仍需二次封装。
四、三种方式对比
维度 | 原生 SQL 分页 | PageHelper + Page | PageHelper + PageInfo |
---|---|---|---|
依赖 | 无 | PageHelper 插件 | PageHelper 插件 |
代码量 | 多(手动处理分页逻辑) | 中等(需类型转换) | 少(直接返回 PageInfo ) |
分页信息 | 需手动封装 | 包含基本分页字段(total、list) | 包含完整分页字段(pages、hasNext等) |
灵活性 | 高(可完全控制 SQL) | 中(依赖插件自动分页) | 低(字段固定) |
适用场景 | 简单分页或禁用第三方库时 | 需要快速实现分页 | 需要完整分页信息且减少代码 |
五、最佳实践建议
-
推荐
PageInfo
:- 适合大多数场景,减少重复代码,直接返回完整分页信息。
- 示例:
public PageInfo<Emp> queryEmp(EmpQueryDTO dto) { PageHelper.startPage(dto.getPageNum(), dto.getPageSize()); List<Emp> list = empMapper.selectByCondition(dto.getDeptId()); return new PageInfo<>(list); }
-
自定义分页实体类:
- 若前端需要特定字段名(如
current
代替pageNum
),可自定义PageResult
:public class PageResult<T> { @JsonProperty("current") private int pageNum; @JsonProperty("size") private int pageSize; private long total; private List<T> records; }
- 若前端需要特定字段名(如
-
性能优化:
- 大数据量分页时,在
startPage
中指定count
优化:PageHelper.startPage(pageNum, pageSize, "COUNT(id)");
- 大数据量分页时,在
-
参数校验:
int pageNum = dto.getPageNum() <= 0 ? 1 : dto.getPageNum(); int pageSize = dto.getPageSize() > 100 ? 100 : dto.getPageSize();
通过以上三种方式,可根据项目需求灵活选择分页实现方案!