RuoYi-Vue-Plus数据分页:对象化分页插件
前言:分页的痛点与解决方案
在传统Web应用开发中,数据分页是一个常见但繁琐的需求。开发者往往需要:
- 手动计算分页参数(当前页码、每页大小)
- 处理SQL的LIMIT和OFFSET语句
- 构建复杂的分页响应结构
- 维护排序逻辑
- 处理边界条件和异常情况
RuoYi-Vue-Plus通过对象化分页插件完美解决了这些问题,提供了统一、简洁、强大的分页解决方案。
核心分页组件解析
PageQuery:统一的分页查询对象
PageQuery 类是分页功能的核心,它封装了所有分页相关的参数:
@Data
public class PageQuery implements Serializable {
// 分页大小
private Integer pageSize;
// 当前页数
private Integer pageNum;
// 排序列
private String orderByColumn;
// 排序方向(desc/asc)
private String isAsc;
// 默认值常量
public static final int DEFAULT_PAGE_NUM = 1;
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
// 构建MyBatis-Plus分页对象
public <T> Page<T> build() {
// 参数校验和默认值处理
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
Page<T> page = new Page<>(pageNum, pageSize);
List<OrderItem> orderItems = buildOrderItem();
if (CollUtil.isNotEmpty(orderItems)) {
page.addOrder(orderItems);
}
return page;
}
}
TableDataInfo:标准的分页响应结构
TableDataInfo 提供了统一的分页响应格式:
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
// 总记录数
private long total;
// 列表数据
private List<T> rows;
// 状态码
private int code;
// 消息内容
private String msg;
// 多种构建方式
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(page.getRecords());
rspData.setTotal(page.getTotal());
return rspData;
}
}
分页功能使用指南
1. 控制器层使用示例
在Controller中,分页使用变得极其简单:
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
@GetMapping("/list")
public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
return userService.selectPageUserList(user, pageQuery);
}
}
2. 服务层实现
服务层接收PageQuery对象并构建分页查询:
@Service
public class SysUserServiceImpl implements ISysUserService {
@Override
public TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
// 构建MyBatis-Plus分页对象
Page<SysUserVo> page = pageQuery.build();
// 执行分页查询
Page<SysUserVo> result = userMapper.selectPageUserList(page, user);
// 返回标准分页响应
return TableDataInfo.build(result);
}
}
3. 数据访问层
Mapper层使用MyBatis-Plus的分页插件:
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
Page<SysUserVo> selectPageUserList(
@Param("page") Page<SysUserVo> page,
@Param("bo") SysUserBo bo
);
}
对应的XML映射文件:
<select id="selectPageUserList" resultType="org.dromara.system.vo.SysUserVo">
SELECT
u.user_id, u.user_name, u.nick_name, u.dept_id,
d.dept_name, u.email, u.phonenumber, u.status,
u.create_time
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
<where>
u.del_flag = '0'
<if test="bo.userName != null and bo.userName != ''">
AND u.user_name LIKE CONCAT('%', #{bo.userName}, '%')
</if>
<if test="bo.status != null and bo.status != ''">
AND u.status = #{bo.status}
</if>
<!-- 更多查询条件 -->
</where>
</select>
高级特性详解
1. 智能排序支持
PageQuery支持多种排序格式:
// 单字段排序
{isAsc:"asc", orderByColumn:"id"}
// → ORDER BY id ASC
// 多字段统一排序
{isAsc:"desc", orderByColumn:"id,createTime"}
// → ORDER BY id DESC, create_time DESC
// 多字段分别排序
{isAsc:"asc,desc", orderByColumn:"id,createTime"}
// → ORDER BY id ASC, create_time DESC
2. 前端排序兼容
自动兼容前端常见的排序类型:
// 自动转换前端排序标识
isAsc = StringUtils.replaceEach(isAsc,
new String[]{"ascending", "descending"},
new String[]{"asc", "desc"}
);
3. SQL注入防护
内置SQL注入防护机制:
// 对排序字段进行安全过滤
String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
orderBy = StringUtils.toUnderScoreCase(orderBy); // 转下划线命名
4. 假分页支持
对于内存中的数据,也提供分页支持:
public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
if (CollUtil.isEmpty(list)) {
return TableDataInfo.build();
}
// 使用Hutool进行内存分页
List<T> pageList = CollUtil.page(
(int) page.getCurrent() - 1,
(int) page.getSize(),
list
);
return new TableDataInfo<>(pageList, list.size());
}
最佳实践指南
1. 统一的分页参数命名
建议前端使用统一的参数名:
pageNum: 当前页码pageSize: 每页大小orderByColumn: 排序字段isAsc: 排序方向
2. 默认值处理策略
// 推荐的处理方式
public TableDataInfo<SysUserVo> list(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize
) {
PageQuery pageQuery = new PageQuery(pageSize, pageNum);
// ... 业务逻辑
}
3. 异常处理
try {
Page<SysUserVo> page = pageQuery.build();
// 执行查询
} catch (ServiceException e) {
// 处理排序参数错误等异常
return TableDataInfo.build().setCode(500).setMsg(e.getMessage());
}
性能优化建议
1. 分页大小限制
// 限制最大分页大小,防止恶意请求
private static final int MAX_PAGE_SIZE = 1000;
public <T> Page<T> build() {
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
pageSize = Math.min(pageSize, MAX_PAGE_SIZE);
// ... 其他逻辑
}
2. 索引优化
确保排序字段和查询条件字段都有合适的索引:
-- 为用户表的常用查询字段创建索引
CREATE INDEX idx_user_status ON sys_user(status);
CREATE INDEX idx_user_dept ON sys_user(dept_id);
CREATE INDEX idx_user_name ON sys_user(user_name);
与其他组件的集成
1. 与Sa-Token权限集成
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
// 权限校验通过后执行分页查询
return userService.selectPageUserList(user, pageQuery);
}
2. 与多租户集成
public TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
Page<SysUserVo> page = pageQuery.build();
// 自动添加租户过滤条件
TenantHelper.execute(() -> {
Page<SysUserVo> result = userMapper.selectPageUserList(page, user);
return TableDataInfo.build(result);
});
}
常见问题解决方案
1. 分页参数传递问题
问题:前端传递的分页参数无法正确解析 解决方案:确保使用正确的参数名和格式
// 前端请求示例
axios.get('/system/user/list', {
params: {
pageNum: 1,
pageSize: 10,
orderByColumn: 'createTime',
isAsc: 'desc'
}
});
2. 排序字段映射问题
问题:前端字段名与数据库字段名不一致 解决方案:使用字段映射或自定义转换逻辑
// 自定义字段映射
private String mapOrderByColumn(String column) {
switch (column) {
case "createTime": return "create_time";
case "userName": return "user_name";
default: return column;
}
}
总结
RuoYi-Vue-Plus的对象化分页插件通过PageQuery和TableDataInfo两个核心类,提供了完整的分页解决方案:
- ✅ 统一规范:标准化的参数传递和响应格式
- ✅ 智能排序:支持多种排序方式和自动转换
- ✅ 安全防护:内置SQL注入防护机制
- ✅ 灵活扩展:支持真分页和假分页场景
- ✅ 生态集成:与权限、多租户等组件无缝集成
通过这套分页方案,开发者可以专注于业务逻辑的实现,而无需关心分页的底层细节,大大提高了开发效率和代码质量。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



