为什么写这篇文章,原因很简单:
1.自己对于若依框架的分页逻辑很好奇
2.自己对于若依框架的分页逻辑不了解
3.自己想要学习人家的分页思想去套用
4.看看人家PageHelper大的底层源码
废话少说,我们直接进入正题——
为了方便分析,我们直接对于若依系统模块中用户相关数据接口作为参考
/**
* 获取用户列表
*/
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
这是若依的获取用户列表,可以看到,这里需要注意的是两点:
startPage()
getDataTable(list)
我们依次来看,先点击startPage();
就会进入到
/** * 设置请求分页数据 */ protected void startPage() { PageUtils.startPage(); }
看来还是未识庐山真面目,我们再进入
/**
* 设置请求分页数据
*/
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);
}
现在差不多了,基本是进入到了PageHelper
最后一层
这段代码是一个用于设置请求分页数据的方法。让我们逐步解释这个方法的功能:
TableSupport.buildPageRequest()
: 从TableSupport
类中调用buildPageRequest
方法,该方法用于构建分页请求的参数。这可能包括当前页码(pageNum
)、每页显示的记录数(pageSize
)、排序字段(orderBy
)等信息。
PageHelper.startPage(pageNum, pageSize, orderBy)
: 使用MyBatis框架的PageHelper
工具类的startPage
方法来启动分页。该方法用传入的页码(pageNum
)、每页显示的记录数(pageSize
)和排序字段(orderBy
)来配置分页信息。
.setReasonable(reasonable)
: 设置分页插件的"reasonable"属性,这是PageHelper
插件的一个配置项。当设置为true
时,如果页码超出范围,则会自动调整为第一页或最后一页;当设置为false
时,如果页码超出范围,则不进行调整。
这段代码的目的是为了简化在MyBatis中进行分页查询的操作。通过调用这个方法,可以方便地从请求中获取分页相关的参数,并配置PageHelper
,使得分页能够在数据库查询中生效。这种做法可以提高代码的可维护性和可读性。
对于startPage();
我们暂且看到这里,我们再看getDataTable()
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;
}
这段代码定义了一个方法 getDataTable
,其主要目的是将查询得到的数据列表包装成一个 TableDataInfo
对象,以便在前端进行展示。
TableDataInfo rspData = new TableDataInfo();
: 创建一个 TableDataInfo
对象,该对象通常用于封装前端展示所需的数据。
rspData.setCode(HttpStatus.SUCCESS);
: 设置 TableDataInfo
对象的状态码为成功状态。这里使用了一个名为 HttpStatus
的类,它可能是一个自定义的枚举类,其中包含了不同状态的常量。在这里,SUCCESS
可能表示成功的状态码。
rspData.setMsg("查询成功");
: 设置成功状态时的消息,通常是一个简短的描述性文本。在这里,消息被设置为 “查询成功”。
rspData.setRows(list);
: 将查询得到的数据列表设置为 TableDataInfo
对象的 rows
属性。这个属性通常用于存储查询结果的数据集合。
rspData.setTotal(new PageInfo(list).getTotal());
: 获取查询结果列表的总记录数,并将其设置为 TableDataInfo
对象的 total
属性。这里使用了 PageInfo
类,它可能是一个与分页相关的工具类,用于获取总记录数等信息。
return rspData;
: 将构建好的 TableDataInfo
对象返回,以便在调用该方法的地方进行处理或响应给前端。
这个方法的作用是将查询得到的数据包装成一个符合特定格式的对象,该对象包含了状态码、消息、数据集合以及总记录数等信息,便于前端进行处理和展示。这种封装可以使前后端的数据交互更加规范和易于处理。
看到这里似乎是了解了怎么用,但是具体如何实现的,我们再接着深入
PageHelper
是一个用于MyBatis框架的分页插件,它简化了在数据库中执行分页查询的操作。下面先是对PageHelper
的底层实现的一些详细介绍:
PageHelper
通过MyBatis的插件机制来实现分页功能。MyBatis允许开发者通过插件拦截SQL语句的执行,这为PageHelper
提供了实现分页的入口点。PageHelper
实现了Interceptor
接口,这是MyBatis插件机制的核心接口。Interceptor
接口定义了在执行SQL语句的前、后以及异常处理时执行的方法,允许插件在这些时机进行拦截和干预。PageHelper
通过一个Page
对象来存储分页相关的信息,包括页码、每页显示的记录数、总记录数等。这个对象是ThreadLocal
线程本地的,确保在同一线程内的分页信息不会相互干扰。ThreadLocal
来保持分页参数的状态,确保在同一线程内的分页查询不会相互影响。PageHelper
的拦截器中,通过解析SQL语句,动态生成对应的分页SQL。这通常包括了在原始SQL语句外层包裹一层分页查询的逻辑,以及计算总记录数的查询。举一个最简单的小例子来演示一下用法,看看在不进行封装的时候,最朴素的用法啥样子
以下是一个简单的示例,演示了如何在MyBatis中使用PageHelper
进行分页查询:
// 导入相关类
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
// 在方法中使用PageHelper进行分页设置
public List<User> getUserList(int pageNum, int pageSize) {
// 使用PageHelper.startPage方法设置分页参数
Page<Object> page = PageHelper.startPage(pageNum, pageSize);
// 执行查询操作,PageHelper会在查询前自动设置分页信息
List<User> userList = userDao.getUserList();
// 通过Page对象获取分页信息
long total = page.getTotal();
// 返回结果,这里通常会将结果封装为一个包含分页信息的对象
return new PageInfo<>(userList, (int) total);
}
在这个例子中:
PageHelper.startPage(pageNum, pageSize)
方法用于设置分页参数,它启动了分页功能,并在当前线程的ThreadLocal
中保存了分页信息。
Page<Object> page
是PageHelper
内部的Page
对象,用于存储分页信息。
执行实际的查询操作,userDao.getUserList()
获取用户列表。
通过page.getTotal()
获取总记录数。
最后,通常会将查询结果和分页信息封装为一个包含分页信息的对象,例如PageInfo
类。
可以看得到,最核心的东西还是在
Page page = PageHelper.startPage(pageNum, pageSize);
这里是官方文档,我们先贴上来
https://pagehelper.github.io/docs/howtouse/ PageHelper官方文档
下来,我们贴上PageHelper
的核心代码,并且进行代码分析解读
以下是对PageHelper
源码的简要注释和分析:
// 导入相关类
.......省略
// PageHelper类实现了Dialect接口和BoundSqlInterceptor.Chain接口
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {
private PageParams pageParams; // 用于处理分页参数的对象
private PageAutoDialect autoDialect; // 自动识别数据库方言的对象
private PageBoundSqlInterceptors pageBoundSqlInterceptors; // 处理BoundSql拦截器的对象
// 无参构造方法
public PageHelper() {
}
// 判断是否需要跳过分页操作
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
Page page = this.pageParams.getPage(parameterObject, rowBounds);
if (page == null) {
return true;
} else {
if (StringUtil.isEmpty(page.getCountColumn())) {
page.setCountColumn(this.pageParams.getCountColumn());
}
this.autoDialect.initDelegateDialect(ms, page.getDialectClass());
return false;
}
}
// 在进行总记录数查询前的操作
public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
return this.autoDialect.getDelegate().beforeCount(ms, parameterObject, rowBounds);
}
// 获取总记录数的SQL语句
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
return this.autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
}
// 在进行总记录数查询后的操作
public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
return this.autoDialect.getDelegate().afterCount(count, parameterObject, rowBounds);
}
// 处理参数对象
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
return this.autoDialect.getDelegate().processParameterObject(ms, parameterObject, boundSql, pageKey);
}
// 在进行分页查询前的操作
public boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
return this.autoDialect.getDelegate().beforePage(ms, parameterObject, rowBounds);
}
// 获取分页查询的SQL语句
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
return this.autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
}
// 获取分页查询的SQL语句(重载)
public String getPageSql(String sql, Page page, RowBounds rowBounds, CacheKey pageKey) {
return this.autoDialect.getDelegate().getPageSql(sql, page, pageKey);
}
// 在进行分页查询后的操作
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
AbstractHelperDialect delegate = this.autoDialect.getDelegate();
return delegate != null ? delegate.afterPage(pageList, parameterObject, rowBounds) : pageList;
}
// 在所有操作完成后的清理工作
public void afterAll() {
AbstractHelperDialect delegate = this.autoDialect.getDelegate();
if (delegate != null) {
delegate.afterAll();
this.autoDialect.clearDelegate();
}
clearPage();
}
// BoundSql拦截链的实现
public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
Page<Object> localPage = getLocalPage();
BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
if (chain == null) {
BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
BoundSqlInterceptor.Chain defaultChain = this.pageBoundSqlInterceptors != null ? this.pageBoundSqlInterceptors.getChain() : null;
if (boundSqlInterceptor != null) {
chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
} else if (defaultChain != null) {
chain = defaultChain;
}
if (chain == null) {
chain = DO_NOTHING;
}
if (localPage != null) {
localPage.setChain((BoundSqlInterceptor.Chain)chain);
}
}
return ((BoundSqlInterceptor.Chain)chain).doBoundSql(type, boundSql, cacheKey);
}
// 设置配置属性
public void setProperties(Properties properties) {
setStaticProperties(properties);
this.pageParams = new PageParams();
this.autoDialect = new PageAutoDialect();
this.pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
this.pageParams.setProperties(properties);
this.autoDialect.setProperties(properties);
this.pageBoundSqlInterceptors.setProperties(properties);
CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
}
}
这段代码是PageHelper
的主要部分,它实现了分页的核心逻辑。以下是对主要方法的简要注释:
skip
: 判断是否需要跳过分页操作。beforeCount
, getCountSql
, afterCount
: 在进行总记录数查询前、获取总记录数的SQL语句、总记录数查询后的操作。processParameterObject
: 处理参数对象,通常在进行分页查询前。beforePage
, getPageSql
, afterPage
: 在进行分页查询前、获取分页查询的SQL语句、分页查询后的操作。afterAll
: 在所有操作完成后的清理工作。doBoundSql
: 处理BoundSql
的拦截链,用于对SQL语句进行拦截和处理。setProperties
: 设置配置属性,包括分页参数、数据库方言、BoundSql拦截器等。PageHelper
通过Dialect
和BoundSqlInterceptor.Chain
接口实现了分页的核心逻辑
大概源码看到这里,文首的1,2,3已经实现,4我们慢慢来,还是非常复杂,我的道行尚浅,我再学习学习,此文后期在做补充…
下次一定——