全栈角度看分页处理(分页调度算法)
mhr18 2024-09-29 14:06 25 浏览 0 评论
作者:京东物流 杨攀
分页是 web application 开发最常见的功能。在使用不同的框架和工具过程中,发现初始行/页的定义不同,特意整理记录。从这个技术点去看不同层的实现。以及不同语言实现的对比。
文章会从正常的web 结构分层的角度去梳理不同层的处理。
分为数据库分页、服务端分页、前端分页
数据库分页
这里用mysql 举例整理。我们常用的数据库例如 Oracle/ SQL Server 等,对于分页语法的支持大同小异。不做具体一一举例。
先从数据库层梳理,也是从最根源去分析分页的最终目的,前端和后端的一起逻辑和适配,都是为了拼接合适的 SQL 语句。
①MySQL LIMIT
语法:[LIMIT {[offset,] row_count}]
LIMIT row_count is equivalent to LIMIT 0, row_count.
The offset of the initial row is 0 (not 1)
参考:MySQL :: MySQL 5.7 Reference Manual :: 13.2.9 SELECT Statement
服务端/后端分页
后端分页,简单讲,就是数据库的分页。 对于mysql 来讲,就是上述 offset row_count 的计算过程。
这里选用了常用的框架组件来对比各自实现的细节。
pagehelper 是Java Orm 框架mybatis 常用的开源分页插件
spring-data-jdbc 是Java 框架常用的数据层组件
①pagehelper
/**
* 计算起止行号 offset
* @see com.github.pagehelper.Page#calculateStartAndEndRow
*/
private void calculateStartAndEndRow() {
// pageNum 页码,从1开始。 pageNum < 1 , 忽略计算。
this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
}
/**
* 计算总页数 pages/ pageCount。
* 在赋值数据总条数的同时,也计算了总页数。
* 可以与 Math.ceil 实现对比看。
*/
public void setTotal(long total) {
if (pageSize > 0) {
pages = (int) (total / pageSize + ((total % pageSize == 0) ? 0 : 1));
} else {
pages = 0;
}
}
SQL 拼接实现: com.github.pagehelper.dialect.helper.MySqlDialect
②spring-data-jdbc
关键类:
org.springframework.data.domain.Pageable
org.springframework.data.web.PageableDefault
/**
* offset 计算,不同于pagehelper, page 页码从0 开始。 default is 0
* @see org.springframework.data.domain.AbstractPageRequest#getOffset
*/
public long getOffset() {
return (long)this.page * (long)this.size;
}
/*
* 总页数的计算使用 Math.ceil 实现。
* @see org.springframework.data.domain.Page#getTotalPages()
*/
@Override
public int getTotalPages() {
return getSize() == 0 ? 1 : (int) Math.ceil((double) total / (double) getSize());
}
/**
* offset 计算,不同于pagehelper, page 页码从0 开始。
* @see org.springframework.data.jdbc.core.convert.SqlGenerator#applyPagination
*/
private SelectBuilder.SelectOrdered applyPagination(Pageable pageable, SelectBuilder.SelectOrdered select) {
// 在spring-data-relation, Limit 抽象为 SelectLimitOffset
SelectBuilder.SelectLimitOffset limitable = (SelectBuilder.SelectLimitOffset) select;
// To read the first 20 rows from start use limitOffset(20, 0). to read the next 20 use limitOffset(20, 20).
SelectBuilder.SelectLimitOffset limitResult = limitable.limitOffset(pageable.getPageSize(), pageable.getOffset());
return (SelectBuilder.SelectOrdered) limitResult;
}
spring-data-commons 提供 mvc 层的分页参数处理器
/**
* Annotation to set defaults when injecting a {@link org.springframework.data.domain.Pageable} into a controller method.
*
* @see org.springframework.data.web.PageableDefault
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PageableDefault {
/**
* The default-size the injected {@link org.springframework.data.domain.Pageable} should get if no corresponding
* parameter defined in request (default is 10).
*/
int size() default 10;
/**
* The default-pagenumber the injected {@link org.springframework.data.domain.Pageable} should get if no corresponding
* parameter defined in request (default is 0).
*/
int page() default 0;
}
MVC 参数处理器: org.springframework.data.web.PageableHandlerMethodArgumentResolver
前端分页
前端展示层,分别从服务端渲染方案以及纯前端脚本方案去看分页最终的页面呈现逻辑。
这里选取的分别是Java 常用的模板引擎 thymeleaf 以及热门的前端框架 element-ui。
从用法以及组件源码角度,去理清终端处理分页的常见方式。
①thymeleaf - 模板引擎
Thymeleaf is a modern server-side Java template engine for both web and standalone environments.
<!-- spring-data-examples\web\example\src\main\resources\templates\users.html-->
<nav>
<!-- class样式 bootstrap 默认的分页用法-->
<ul class="pagination" th:with="total = ${users.totalPages}">
<li th:if="${users.hasPrevious()}">
<a th:href="@{/users(page=${users.previousPageable().pageNumber},size=${users.size})}" aria-label="Previous">
<span aria-hidden="true">?</span>
</a>
</li>
<!-- spring-data-examples 分页计算从0 开始, /users?page=0&size=10 -->
<!-- 生成页码列表,呈现形式即我们常见的 ①②③④ -->
<li th:each="page : ${#numbers.sequence(0, total - 1)}"><a th:href="@{/users(page=${page},size=${users.size})}" th:text="${page + 1}">1</a></li>
<li th:if="${users.hasNext()}">
<!-- 下一页实现,因为是服务器端渲染生成。在最终生成的html 中,这里的href 是固定的。实现思路和纯前端技术,使用javascript 脚本对比看 -->
<a th:href="@{/users(page=${users.nextPageable().pageNumber},size=${users.size})}" aria-label="Next">
<span aria-hidden="true">?</span>
</a>
</li>
</ul>
</nav>
②element-ui 前端框架
// from node_modules\element-ui\packages\pagination\src\pagination.js
// page-count 总页数,total 和 page-count 设置任意一个就可以达到显示页码的功能;
computed: {
internalPageCount() {
if (typeof this.total === 'number') {
// 页数计算使用 Math.ceil
return Math.max(1, Math.ceil(this.total / this.internalPageSize));
} else if (typeof this.pageCount === 'number') {
return Math.max(1, this.pageCount);
}
return null;
}
},
/**
* 起始页计算。 page 页码从1 开始。
*/
getValidCurrentPage(value) {
value = parseInt(value, 10);
// 从源码的实现可以看到,一个稳定强大的开源框架,在容错、边界处理的严谨和思考。
const havePageCount = typeof this.internalPageCount === 'number';
let resetValue;
if (!havePageCount) {
if (isNaN(value) || value < 1) resetValue = 1;
} else {
// 强制赋值起始值 1
if (value < 1) {
resetValue = 1;
} else if (value > this.internalPageCount) {
// 数据越界,强制拉回到PageCount
resetValue = this.internalPageCount;
}
}
if (resetValue === undefined && isNaN(value)) {
resetValue = 1;
} else if (resetValue === 0) {
resetValue = 1;
}
return resetValue === undefined ? value : resetValue;
},
总结
- 技术永远是关联的,思路永远是相似的,方案永远是相通的。单独的去分析某个技术或者原理,总是有边界和困惑存在。纵向拉伸,横向对比才能对技术方案有深刻的理解。在实战应用中,能灵活自如。
- 分页实现的方案最终是由数据库决定的,对于众多的数据库,通过SQL 语法的规范去框定,以及我们常用的各种组件或者插件去适配。
- 纵向对比,我们可以看到不同技术层的职责和通用适配的实现过程,对于我们日常的业务通用开发以及不同业务的兼容有很大的借鉴意义。
- 横向对比,例如前端展示层的实现思路,其实差别非常大。如果使用 thymeleaf,结构简单清晰,但交互响应上都会通过服务器。如果选用element-ui,分页只依赖展示层,和服务端彻底解构。在技术选型中可以根据各自的优缺点进行适度的抉择。
相关推荐
- 一文带您了解数据库的行列之争:行式与列式存储的异同
-
数据库存储格式是数据库管理系统中一个至关重要的方面,它直接影响到数据的组织和检索效率。在数据库中,有两种主要的存储格式,即行式存储和列式存储。这两者采用截然不同的方法来组织和存储数据,各自具有一系列优...
- NL2SQL(三)开源项目怎么选:talk is cheap, show me the code!
-
老规矩,先看效果下面的demo来自试用的SuperSonic,将会在下面详细介绍:大模型时代Text-to-SQL特点随着基于LLM技术的发展,RAG/AIAgent/Fine...
- JDK25长期支持版九月降临:18项王炸功能全解析
-
Java要放大招啦!9月份推出的JDK25长期支持版已经锁定18个超能力,从稳定值到结构化并发,还有Linux系统下的"预知未来"性能分析!下面我用打游戏的术语给你们掰扯明白:1、飞...
- OceanBase 推出单机版 高度兼容MySQL和Oracle
-
【环球网科技综合报道】3月27日,独立数据库厂商OceanBase正式发布单机版产品。据悉,这一产品基于自主研发的单机分布式一体化架构设计,具备极简数据库架构和高度兼容性,为中小规模业务提供兼具性能与...
- 黄远邦:应对7月1日闰秒对Oracle数据库影响
-
由于今年7月1日全世界会多出一秒,这可能对时间敏感的IT系统造成较大影响。中亦科技数据库团队对此问题做了深入的研究,并对用户系统提出了相应的解决方法及建议。中亦科技数据库产品总监黄远邦认为,闰秒调整会...
- MySQL数据库密码忘记了,怎么办?(mysql 数据库密码)
-
#头条创作挑战赛#MySQL数据库密码忘记了且没有其他可以修改账号密码的账户时怎么办呢?登录MySQL,密码输入错误/*密码错误,报如下错误*/[root@TESTDB~]#mysql-u...
- Chinese AI Talent in Spotlight as Nvidia and Meta Escalate Talent War
-
OntherightisBanghuaZhu,ChiefResearchScientistatNVIDIATMTPOST--SiliconValley’stoptech...
- 用Cursor开启JAVA+AI生涯(javascirpt怎么开启)
-
Cursor是基于VSCode开发的一款编辑器,支持多种语言的开发编辑。与传统的开发工具相比,它有多种优势:与AI无缝集成,响应速度快,占用内存小。但很多同学在"起步"过程中遇到了...
- 毕业十年了,自从做了开发用了很多软件,但距离写开发工具还很远
-
办公系统类:办公软件Word、Excel、PowerPoint三大必备技能+腾讯/金山在线文档解压缩操作:7-zip/winrar文件文本处理:Notepad++(文本编辑器正则表达式超级好...
- 盘点Java中最没用的知识⑤:这3个老古董你还在代码里“考古”?
-
一、Stack类:“继承Vector”的历史bug,为何成了性能拖油瓶?你是不是在学Java集合时,老师说过“栈结构用Stack类”?是不是在老代码里见过"newStack<>(...
- Gemini 2.5 Pro 0506发布,编程最强大模型, 碾压 Claude3.7 sonnent
-
一、Gemini2.5Pro(I/Oedition)发布1、为何叫I/Oedition?谷歌史上最强编程模型Gemini2.5Pro(I/Oedition)发布,具体型号是Gemin...
- 如何让无聊变得有趣(附本人大量美图)
-
文/图:金冬成在这条长300公里的公路上,我已经来回往返了无数次。3小时车程,一个人,想想都是多么无聊的一件事。其实,人生道路上,类似这种无聊的事情有很多很多。无聊的事情、枯燥的工作,往往让我们容易失...
- Oracle 推出 Java 24,增强 AI 支持和后量子加密
-
导读:Oracle宣布正式发布Java24,该语言增加了几个新功能,例如StreamGatherersAPI和Class-FileAPI的可用性,以及专门为AI推理和量子安全设计...
- 公司ERP突然变慢?“索引重建”这颗“药”可不能随便吃!
-
各位老板、IT小哥、财务小姐姐,有没有遇到过公司ERP系统突然卡顿得像“老爷车”,点个按钮半天没反应,急得直跺脚?这时候,可能有人会跳出来说:“我知道,重建一下数据库索引就好了!”听起来像个“神操作”...
- 基于Java实现,支持在线发布API接口读取数据库,有哪些工具?
-
基于java实现,不需要编辑就能发布api接口的,有哪些工具、平台?还能一键发布、快速授权和开放提供给第三方请求调用接口的解决方案。架构方案设计:以下是一些基于Java实现的无需编辑或只需少量编辑...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 一文带您了解数据库的行列之争:行式与列式存储的异同
- NL2SQL(三)开源项目怎么选:talk is cheap, show me the code!
- JDK25长期支持版九月降临:18项王炸功能全解析
- OceanBase 推出单机版 高度兼容MySQL和Oracle
- 黄远邦:应对7月1日闰秒对Oracle数据库影响
- MySQL数据库密码忘记了,怎么办?(mysql 数据库密码)
- Chinese AI Talent in Spotlight as Nvidia and Meta Escalate Talent War
- 用Cursor开启JAVA+AI生涯(javascirpt怎么开启)
- 毕业十年了,自从做了开发用了很多软件,但距离写开发工具还很远
- 盘点Java中最没用的知识⑤:这3个老古董你还在代码里“考古”?
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)