您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
全栈角度看分页处理
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
全栈角度看分页处理
完成设置321
2023-06-20
IP归属:北京
6720浏览
> 分页是 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](https://dev.mysql.com/doc/refman/5.7/en/select.html) ## 服务端/后端分页 > 后端分页,简单讲,就是数据库的分页。 对于mysql 来讲,就是上述 offset row_count 的计算过程。 > 这里选用了常用的框架组件来对比各自实现的细节。 > pagehelper 是Java Orm 框架mybatis 常用的开源分页插件 > spring-data-jdbc 是Java 框架常用的数据层组件 ### ①pagehelper ```java /** * 计算起止行号 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); } ``` ```java /** * 计算总页数 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 ```java /** * 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()); } ``` ```java /** * 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 层的分页参数处理器 ```java /** * 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. ```html <!-- 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 前端框架 ```javascript // 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,分页只依赖展示层,和服务端彻底解构。在技术选型中可以根据各自的优缺点进行适度的抉择。
上一篇: h2database BTree 设计实现与查询优化思考
下一篇:稳,从数据库连接池 testOnBorrow 看架构设计
完成设置321
文章数
7
阅读量
1155
作者其他文章
01
稳,从数据库连接池 testOnBorrow 看架构设计
本文从 Commons DBCP testOnBorrow 的作用机制着手,管中窥豹,从一点去分析数据库连接池获取的过程以及架构分层设计。以下内容会按照每层的作用,贯穿分析整个调用流程。1️⃣框架层 commons-poolThe indication of whether objects will be validated before being borrowed from the pool.
01
分布式数据库 Join 查询设计与实现浅析
相对于单例数据库的查询操作,分布式数据查询会有很多技术难题。本文记录 Mysql 分库分表 和 Elasticsearch Join 查询的实现思路,了解分布式场景数据处理的设计方案。文章从常用的关系型数据库 MySQL 的分库分表Join 分析,再到非关系型 ElasticSearch 来分析 Join 实现策略。逐步深入Join 的实现机制。①Mysql 分库分表 Join 查询场景分库分表场
01
这问题巧了,SpringMVC 不同参数处理机制引发的思考
这个问题非常有趣,不是SpringMVC 的问题,是实际开发中混合使用了两种请求方式暴露出来的。问题场景功能模块中,提供两个 Http 服务。一个是列表查询(application/json 请求),一个是列表导出(表单请求)。运行环境发现个问题:MVC model 新添加的属性,类似的 Http 请求,一个有值,一个没有代码如下:/** * application/json 请求。 这种情况 p
01
h2database BTree 设计实现与查询优化思考
h2database 是使用Java 编写的开源数据库,兼容ANSI-SQL89。即实现了常规基于 BTree 的存储引擎,又支持日志结构存储引擎。功能非常丰富(死锁检测机制、事务特性、MVCC、运维工具等),数据库学习非常好的案例。本文理论结合实践,通过BTree 索引的设计和实现,更好的理解数据库索引相关的知识点以及优化原理。BTree 实现类h2database 默认使用的 MVStore
完成设置321
文章数
7
阅读量
1155
作者其他文章
01
稳,从数据库连接池 testOnBorrow 看架构设计
01
分布式数据库 Join 查询设计与实现浅析
01
这问题巧了,SpringMVC 不同参数处理机制引发的思考
01
h2database BTree 设计实现与查询优化思考
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号