若依(ruoyi)分页代码解析

介绍

若依(RuoYi)是一款广受好评的后端管理框架,以其高效、灵活的特点深受开发者喜爱。它完全免费提供给个人和企业使用,并在gitee上获得了超过37.1K颗星的关注与支持。为了深入学习其优秀的设计理念和技术实现,我下载了该框架进行研究。

版本信息

我运行的版本是RuoYi-Mybatis-Plus:RuoYi-Vue + MybatisPlus 纯净版、项目全栈脚手架,下载地址 https://gitee.com/tellsea/ruoyi-vue-plus

学习提要

在学习若依的代码时候,我发现他的分页是这样的:

/**  
* 获取通知公告列表  
*/  
@PreAuthorize("@ss.hasPermi('system:notice:list')")  
@GetMapping("/list")  
public TableDataInfo<?> list(SysNotice notice) {  
    startPage();  
    List<SysNotice> list = noticeService.selectNoticeList(notice);  
    return getDataTable(list);  
}

而请求路径是这样的

http://localhost:8080/ruoyi-vue-service/system/notice/list?pageNum=3&pageSize=2

跟我之前使用使用MybatisPlus时写法不一样,

/**
* 这是我写分页时常用的写法
*/
@PostMapping("page")  
public R<IPage<Blog>> page(@RequestBody BasePageEntity param){  
    Page<Blog> page = new Page<>(param.getCurrent(), param.getSize());  
    return R.ok(blogService.page(page));  
}

于是我对这个分页的实现产生了学习的欲望!!!

代码解析

startPage()代码解析

import com.github.pagehelper.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 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();  
}

如代码所示,就是从Reques里面拿到请求参数封装分页对象,然后给到PageHelper.startPage()

PageHelper 源码解析

1. 插件注册

首先,PageHelper 是通过 MyBatis 的插件机制来实现的。它实现了 Interceptor 接口,并且在配置时会将自己注册为一个拦截器。这通常是在 Spring Boot 的配置类中完成的,例如:

@Bean
public PageHelper pageHelper() {
    PageHelper pageHelper = new PageHelper();
    Properties p = new Properties();
    // 设置分页参数
    pageHelper.setProperties(p);
    return pageHelper;
}
2. ThreadLocal 存储分页信息

PageHelper 使用了 ThreadLocal 来存储分页参数,确保每个线程都有自己独立的副本,从而保证线程安全。具体来说,PageHelper 维护了一个名为 LocalPage 的内部静态类,该类使用 ThreadLocal 来保存分页信息:

private static class LocalPage {
    private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<>();
    
    public static void localPage(Page page) {
        LOCAL_PAGE.set(page);
    }
    
    public static Page localPage() {
        return LOCAL_PAGE.get();
    }
    
    public static void clearPage() {
        LOCAL_PAGE.remove();
    }
}
3. 开启分页

当你调用 PageHelper.startPage() 方法时,实际上是创建了一个 Page 对象,并将其存入 ThreadLocal 中。这个方法也负责设置分页的参数,如当前页码和每页显示的记录数。

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {  
    Page<E> page = new Page(pageNum, pageSize, count);  
    page.setReasonable(reasonable);  
    page.setPageSizeZero(pageSizeZero);  
    Page<E> oldPage = getLocalPage();  
    if (oldPage != null && oldPage.isOrderByOnly()) {  
    page.setOrderBy(oldPage.getOrderBy());  
    }  

    setLocalPage(page);  
    return page;  
}
4. SQL 拦截与修改

PageHelper 实现了 Interceptor 接口中的 intercept 方法。当 MyBatis 准备执行 SQL 语句时,会触发这个方法。在这里,PageHelper 会检查 ThreadLocal 中是否存在有效的分页信息。如果存在,则根据当前使用的数据库类型对原始 SQL 进行修改,添加相应的分页逻辑(比如 MySQL 的 LIMITOFFSET)。

public Object intercept(Invocation invocation) throws Throwable {  
    Object var16;  
    try {  
        // 省略部分代码  
        List resultList;  
        // 执行sql
        resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);  
        // 组装结构
        var16 = this.dialect.afterPage(resultList, parameter, rowBounds);  

    } finally {  
        // 清理ThreadLocal
        if (this.dialect != null) {  
        this.dialect.afterAll();  
    }  
    }  
    return var16;  
}
5. 分页信息清理

一旦 SQL 语句被执行完毕,PageHelper 会在同一个事务中清理 ThreadLocal 中的分页信息,以确保后续的 SQL 查询不会受到之前分页设置的影响。

// 在适当的时机调用此方法来清除分页信息
LocalPage.clearPage();
6. 返回分页数据

对于每次分页查询的结果,PageHelper 会返回一个封装了分页信息的对象,通常是 PageInfo 或者是自定义的分页对象,里面包含了查询结果列表、总记录数等信息。

7. 支持嵌套调用

为了支持在一个线程内多次调用 startPage(),即嵌套调用,PageHelper 维护了一个栈结构来保存分页信息。每当调用 startPage() 时,都会将当前分页信息压入栈中;而在 SQL 查询完成后,则从栈中弹出恢复之前的分页状态。

结论

若依使用com.github.pagehelper.PageHelper 是一个为 MyBatis 提供分页查询功能的第三方插件,它可以在不修改 SQL 语句的情况下,通过简单的 API 调用实现对数据库查询结果的分页。这个插件适用于多种主流的关系型数据库,并且与 MyBatis 的集成非常简单。

注意事项

  • PageHelper 默认只对紧跟着它的第一个 SQL 语句生效。
  • 如果在一个事务中多次使用 PageHelper 分页,确保每次分页都正确地调用了 PageHelper.startPage() 方法。
  • PageHelper 支持多种数据库方言,确保配置时选择了正确的数据库类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值