自学内容网 自学内容网

基于若依框架的动态分页逻辑的实现分析

如果让我们自己写分页查询的逻辑,应该怎么写呢?

在前端要完成分页的逻辑实际要做的工作还是挺多的。

  1. 分页查询应该支持查询参数的输入,对于一个有众多属性的列表,可能有很多查询参数,对于不同的参数类型,有的需要like模糊查找、有的需要equals相等、有的必须是一个between时间范围。
  2. 分页查询要有一个查询区的页面组件提供查询参数的输入,这个要考虑查询区的形式,提供平铺式的,还是提供一个简单的搜索输入框,要考虑是否记录历史值,使用get还是post发送请求。
  3. 分页组件做页面切换时需要实时改变分页参数,当页数较小时应该简便显示,当页数较多时应该详细显示组件。同时也要选择合适的条数。
  4. 表格的某个字段需要做筛选、排序、过滤时,也需要分页查询接口提供支持。

因此、分页查询功能的完成其实也不简单。

后端对于分页查询的逻辑编写,其实也不简单。

例如,对于众多变化的查询参数应该用什么请求对象来接收。用HashMap?还是专门的DTO请求对象?

支持众多可选查询参数的接口,有的属性会传入查询值,有的又没有传,难道我要写很多个if条件来判断?

分页参数又是怎么影响sql语句做到分页分段查询的?

接下来,我们分析下在若依框架中的分页逻辑是怎么完成的。

一、前端调用实现

(一)分页变量定义

将分页变量放入查询参数中,并将查询参数提取出来,防止嵌套过深。

const data = reactive({
 form: {},
  // 一般在查询参数中定义分页变量
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    //userName: "", 其它查询参数
  },
  rules: {}
});
// 提取出变量,防止嵌套过深
const { queryParams, form, rules } = toRefs(data);

(二)查询区

一般分页查询是支持传入参数后进行搜索的,因此在前端页面有个填写查询参数的查询区。

<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px">
   <el-form-item label="用户名称" prop="roleName">
      <el-input
         v-model="queryParams.userName"
         placeholder="请输入用户名称"
         clearable
         style="width: 240px"
         @keyup.enter="handleQuery"
      />
   </el-form-item>
   <!-- 其它查询参数项-->
   <el-form-item>
      <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
      <el-button icon="Refresh" @click="resetQuery">重置</el-button>
   </el-form-item>
</el-form>

(三)分页组件

分页组件可以更改分页参数,并接收后台返回的总条数total,若total为0则不显示分页组件。

// 页面添加分页组件,传入分页变量
<pagination
  v-show="total>0"
  :total="total"
  :page.sync="queryParams.pageNum"
  :limit.sync="queryParams.pageSize"
  @pagination="getList"
/>

(四)调用方法

/** 查询用户列表 */
function getList() {
   loading.value = true;
   // 调用后台方法,传入参数 获取结果
   listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
      loading.value = false;
      // 接受数据集合
      userList.value = res.rows;
      // 接受数据总条数
      total.value = res.total;
   });
};

(五)前端接口方法

这里可以看到分页查询的请求是GET请求,而不是POST请求。

查询参数以?name1=value1&name2=value2的形式发送请求,优点是可以进行浏览器缓存,方便记录查询日志。缺点是若查询参数很长很复杂,或需要保密,则还是用POST请求比较好。

import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi";

// 查询用户列表
export function listUser(query) {
  return request({
    url: '/system/user/list',
    method: 'get',
    params: query
  })
}

二、后端逻辑实现

(一)控制器Controller

对接前端接口的请求路径、方法的内容在Controller文件中。

@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController

@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
    startPage();  // 此方法配合前端完成自动分页
    List<SysUser> list = userService.selectUserList(user);
    return getDataTable(list);
}
}

可以看到,前端控制器继承了一个基类控制器BaseController,该基类控制器委托给分页工具类PageUtils进行分页功能的处理。

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

public class BaseController
{
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 设置请求分页数据
     */
    protected void startPage()
    {
        PageUtils.startPage();
    }

    /**
     * 设置请求排序数据
     */
    protected void startOrderBy()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
        {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            PageHelper.orderBy(orderBy);
        }
    }

    /**
     * 清理分页的线程变量
     */
    protected void clearPage()
    {
        PageUtils.clearPage();
    }
}

(二)分页工具类

实际上,分页工具类又是委托给PageHelper插件进行分页处理。

import com.github.pagehelper.PageHelper;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.sql.SqlUtil;

public class PageUtils extends PageHelper
{
    /**
     * 设置请求分页数据
     */
    public static void startPage()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
        Boolean reasonable = pageDomain.getReasonable();
        // 委托给分页插件处理
        PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
    }

    /**
     * 清理分页的线程变量
     */
    public static void clearPage()
    {
        PageHelper.clearPage();
    }
}

分页插件中内部深层调用了PageMethod中的startPage方法

public abstract class PageMethod {
// 线程变量
    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
    
    public static <E> Page<E> startPage(Object params) {
        Page<E> page = PageObjectUtil.getPageFromObject(params, true);
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }

        setLocalPage(page);
        return page;
    }
    // 设置线程变量
    public static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

    public static <T> Page<T> getLocalPage() {
        return (Page)LOCAL_PAGE.get();
    }
}

(三)表格数据处理

由于表格列表的按字段排序的功能也需要调用分页查询接口,这里封装了分页的属性名称的默认值。

public class TableSupport
{
    /** 当前记录起始索引 */
    public static final String PAGE_NUM = "pageNum";

    /** 每页显示记录数 */
    public static final String PAGE_SIZE = "pageSize";

    /** 排序列 */
    public static final String ORDER_BY_COLUMN = "orderByColumn";

    /** 排序的方向 "desc" 或者 "asc". */
    public static final String IS_ASC = "isAsc";

    /** 分页参数合理化 */
    public static final String REASONABLE = "reasonable";

    /** 封装分页对象 */
    public static PageDomain getPageDomain()
    {
        PageDomain pageDomain = new PageDomain();
        pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
        pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10));
        pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
        pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
        pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
        return pageDomain;
    }

    public static PageDomain buildPageRequest()
    {
        return getPageDomain();
    }
}

然后创建分页对象PageDomain交给分页插件PageHelper使用。

// 分页参数的封装
public class PageDomain
{
    /** 当前记录起始索引 */
    private Integer pageNum;

    /** 每页显示记录数 */
    private Integer pageSize;

    /** 排序列 */
    private String orderByColumn;

    /** 排序的方向desc或者asc */
    private String isAsc = "asc";

    /** 分页参数合理化,例如:前端传递的参数是:pageNum=-1是不合理的 */
    private Boolean reasonable = true;
}

由于pagehelper的插件只对mybatis有用,如果自己需要对别的数据来源进行分页。

可以参照框架使用一部分功能,例如以下是对list集合进行分页:

 public TableDataInfo getTodoItems(@RequestParam String searchValue) {
        /**第一步:pageNum和pageSize是从前端数据里传进来的分页对象的属性**/
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        String userName = SecurityUtils.getUsername();
 
        /**第二步:过滤并获取数据**/
        List<TodoTaskDTO> result = taskCenterService.getTodoItem(searchValue);
        
        /**第四步:获取处理好的list集合**/
        int num = result.size();
        
        // 对列表进行分页
        result = result.stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
        // 组装响应数据
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(0);
        rspData.setRows(result);
        rspData.setTotal(num);
        return rspData;
    }

(四)实体基类

注意到实体类,例如SysUser类既作为请求对象,又是ORM映射的数据库表实体对象。

这里共用了同一个对象,简化了类的数量。但是否会出现功能不适配呢?

public class SysUserController extends BaseController
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
    startPage();  // 此方法配合前端完成自动分页
    List<SysUser> list = userService.selectUserList(user);
    return getDataTable(list);
}
}

public class SysUser extends BaseEntity{
private Long userId;
    private String userName;
    // 省略其它...
}

在这里插入图片描述
可以在请求URL的携带参数中看到,日期范围是含在params的数组里的。

http://localhost:8811/dev-api/system/user/list?pageNum=1&pageSize=10&userName=admin&params%5BbeginTime%5D=2025-01-01&params%5BendTime%5D=2025-01-03

SysUser 类继承了BaseEntity基类,基类中包含如下属性:

  • searchValue :搜索值
  • createBy :创建人,记录创建该实体的对象或用户的标识符,可以是用户名、用户ID等。
  • createTime :创建时间,使用 @JsonFormat 注解来指定日期时间格式,以便于JSON序列化/反序列化时保持一致的格式。
  • updateBy :更新人,记录最后一次更新该实体的对象或用户的标识符。
  • updateTime :更新时间,使用 @JsonFormat 注解来指定日期时间格式,以便于JSON序列化/反序列化时保持一致的格式。
  • remark :提供额外的备注信息,可以用于描述实体的任何相关信息。
  • params :请求参数,用于传递额外的参数或非结构化数据
public class BaseEntity implements Serializable
{
    private static final long serialVersionUID = 1L;

    /** 搜索值 */
    @JsonIgnore
    private String searchValue;

    /** 创建者 */
    private String createBy;

    /** 创建时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    /** 更新者 */
    private String updateBy;

    /** 更新时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    /** 备注 */
    private String remark;

    /** 请求参数 */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private Map<String, Object> params;
}

(五)动态SQL

这里接收各种参数查询的功能是通过使用mybatis的动态sql的方式完成的。

应该通过代码生成工具自动生成以下代码,否则会有点繁琐。

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
</if>
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
<if test="status != null and status != ''">
AND u.status = #{status}
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(u.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
</if>
<!-- 数据范围过滤 -->
${params.dataScope}
</select>

若使用MyBatis-Plus 的功能,那么不用生成动态sql的代码,但是可能需要用以下方式实现功能。


QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
// 使用 lambda 表达式安全地添加条件
wrapper.like(StringUtils.isNotEmpty(userName), SysUser::getUserName, userName);
// 如果有其他条件,继续添加...
if (StringUtils.isNotEmpty(phonenumber)) {
    wrapper.like(SysUser::getPhonenumber, phonenumber);
}
if (status != null && !"".equals(status)) {
    wrapper.eq(SysUser::getStatus, status);
}

// 处理日期范围查询
if (params.getBeginTime() != null) {
    wrapper.ge(SysUser::getCreateTime, params.getBeginTime());
}
if (params.getEndTime() != null) {
    wrapper.le(SysUser::getCreateTime, params.getEndTime());
}
// 执行查询
List<SysUser> userList = mapper.selectList(wrapper);

对于复杂查询,特别是涉及到多表关联、分页、排序、数据权限等功能时,MyBatis-Plus 虽然提供了很多便利的方法,但在某些情况下还是需要结合 XML 配置或手写 SQL 来满足特定需求。

(六)返回响应数据

获取到的数据是通过封装TableDataInfo 返回给前端的。


import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

public class BaseController
{
    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected TableDataInfo getDataTable(List<?> list)
    {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(new PageInfo(list).getTotal());
        return rspData;
    }
}

@Schema(title = "表格分页数据对象")
public class TableDataInfo implements Serializable
{

    /** 总记录数 */
    @Schema(title = "总记录数")
    private long total;

    /** 列表数据 */
    @Schema(title = "列表数据")
    private List<?> rows;

    /** 消息状态码 */
    @Schema(title = "消息状态码")
    private int code;

    /** 消息内容 */
    @Schema(title = "消息内容")
    private String msg;
}

原文地址:https://blog.csdn.net/qq_40610003/article/details/145197484

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!