介绍
若依(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 的 LIMIT
和 OFFSET
)。
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 支持多种数据库方言,确保配置时选择了正确的数据库类型。