PageHelper最佳实践

一. 开发准备

1. 开发工具

IntelliJ IDEA 2020.2.3

2. 开发环境

Red Hat Open JDK 8u256

Apache Maven 3.6.3

3. 开发依赖

SpringBoot

MyBatis

PageHelper

二. 技术文档

1. 基于SpringBoot

SpringBoot 官方文档

SpringBoot 中文社区

2. 基于MyBatis

MyBatis 官方文档

3. 集成PageHelper

PageHelper 开源仓库

三. 应用讲解

1. 基本使用

在实际项目运用中,PageHelper的使用非常便利快捷,仅通过 + 两个类,就足以完成分页功能,然而往往这种最简单的集成使用方式,却在很多实际应用场景中,没有得到充分的开发利用.

接下来是我们最常见的使用方式:

在某种程度上而言,上述写法的确是符合PageHelper的使用规范 :

在集合查询前使用,并且中间不能穿插执行其他SQL

但是作为Developer的我们,往往只有在追求完美和极致的道路上才能够寻得突破和机遇; 以下是合理且规范的基本使用:

FAQ1. 为什么要重新声明一个list函数?

答: 往往在很多实际业务应用场景中,分页查询是基于大数据量的表格展示需求来进行的.

然而很多时候,譬如: 内部服务的互相调用,的提供.

甚至在某些前后端分离联调的业务场景中,是同样需要一个非分页集合查询接口来提供服务的.

另外,暂时以上因素抛开不谈,我们可以根据上述写法来定义和规范某些东西

譬如: 分页和集合查询的分离和解耦(解耦详情请看进阶使用),

分页请求的请求和响应与实际业务参数的分离(详情请看进阶使用)等等…

2. 是什么?

答: 是函数返回的默认实例内置的函数,该函数可以用以的形式通过额外的来进行查询而不需要再进行多余的与转换,而的参数则是内置的接口用以达到转换的目的

3. 这种写法的代码量看起来不少反多?

答: 正如同中所描述的,就代码量而言,确实没有更进一步的简化,但是再某些业务场景中,在已具有函数接口的情况下,是一种更直观的优化(优化详情请看进阶使用)

2. 进阶使用

先看代码,再谈解析:

可以看到可以作为全局通用接口的封装和声明 而作为通用分页接口page函数却在此处利用特有关键字 直接声明了函数的方法体

在中我们看到了一个新的,参考了用以包装/声明/分离分页参数和业务参数,且参数类型为泛型,即支持任何数据类型的业务参数 同时也可以看到实现了接口,并且多了一个属性字段

在实际应用中,只需要声明我们通用的业务查询请求参数和响应结果即可

实现类中也只需要重写方法体,将实际业务场景中需要处理的业务逻辑处理和查询方法写入其中,并不需要关心分页功能

最后编码接口时,也只需要直接调用即可,而请求参数直接用包装,将分页参数和业务参数分离,在前后端接口联调中,保持这种分离规范,可以很大程度上的降低沟通和开发成本

FAQ1. 作为,为什么可以声明方法体?

答: 中新特性之一就是为接口类增加了方法,即声明方法后,其子类或实现都将默认具有这些方法,可以直接调用 而在此处为方法声明是因为函数只关注分页参数和分页响应,脱离了业务场景,方法体大相径庭,所以索性抽象定义出来,免去了其实现的复杂冗余过程

2. 的声明有什么意义?实现是为了什么?

答: 是参考编写的类(不确定往后是否会封装此类,兴许我可以提个上去,也参与开源框架的开发) 编写此类的目的就是为了分离分页和业务数据,让开发者专注于业务的实现和开发,同时也是对分页查询的一种规范,无论是请求还是响应都将分页相关的数据抽离出来,单独使用 而实现则是因为作为内置的,在不了解它更多意义上的作用前,可以作为我们分页参数声明的一种规范,而中也只声明了三个方法,分别是的方法,另外在源码分析中,我将会提到实现此接口更深层的意义

3. 中除了常规的,为什么还需要一个?

答: 常规的分页查询中只需要即可完成分页的目的,但是往往伴随着分页查询的还有筛选排序,而则是专注基于SQL的动态传参排序

4. 如何使用?会有什么问题吗?

答: 和一样,都是通过拦截器,在query查询中注入进去的,所以在前端传参时,参数应为数据库这种形式,多字段排序则可以用逗号(,)拼接,譬如: , 但是另外一方面又存在两个问题, 第一就是大多数数据库表字段设计中,都会使用蛇形case命名,而非常规开发中的驼峰case命名,所以存在一层转换,而这种转换可以分配给前端传参时,也可以分配给后端接参时. 第二就是这样赤裸裸的将排序字段暴露在接口中,会存在order by SQL注入的风险,所以在实际使用过程中,我们需要通过某些手段去校验和排查的传参是否合法,譬如用正则表达式匹配参数值只能含有语法中必要的值,例如字段名,,不允许包含特殊字符/数据库关键字等

5. 一定需要给默认值吗?

答: 通过阅读PageHelper源码,我们得知在查询参数为null时,它并不会赋予它们默认值,并不进行额外的处理,以至于导致分页失败,而给默认值,也是为了谨防前后端调试接口过程中可能会出现的各种意外

3. 源码分析

首先我们看过程中发生了什么 :

这是继承(extend)的抽象类中的一个静态方法

再看代码第一行 发生了什么:

可以看到在此方法中,会先判断是否为null,再而通过判断是否为的子类或实现类 如果以上两个 皆不满足,则则会在我省略贴出的代码中通过大量的反射代码来获取以及. 总所皆知,反射在Java中虽然广泛应用,并且作为语言独有特性之一,深受广大开发者的喜爱,但是反射在某种程度上,是需要性能成本的,甚至于现阶段很多主流的框架和技术,都在尽量减少反射的运用,以防止框架性能过差,被市场淘汰. 那么到此为止,我们也终于解释并知道了为什么要实现接口了,在此处的代码中可以直接通过接口获取到分页参数,而不需要通过有损性能的反射获取需要的参数

继续看中的后续代码:

可以看到继承的抽象类中声明了一个的线程本地变量,而则是为了获取当前线程中的而接下来则是判断是否存在旧分页数据 此处的通过函数我们可以知道,当只存在参数时,即为也就是说,当存在旧分页数据并且旧分页数据只有排序参数时,就将旧分页数据的排序参数列入新分页数据的排序参数 然后将新的分页数据存入本地线程变量中 实际应用场景中,这种情况还是比较少,仅排序而不分页,所以某种角度上而言,我们仅当了解便好

接下来再看 中发生了什么:

可以看到,该方法的实现非常简单明了,就是通过注册声明接口由开发自定义集合查询方式并由它内部执行,随后便返回实体 前面我们有提到,基于拦截器达到分页的目的,那么为什么此处的执行,就可以返回实体呢? 实际上这便是拦截器的妙用所在,在执行时,会触发自定义的查询拦截器,并通过解析SQL和SQL参数,根据数据库类型,进行分页,譬如MySQL的,Oracle的等, 同时还会在我们定义的查询SQL之前,会重新生成一条select count(*)的SQL率先执行,已达到它定义内置分页参数的目的

以上便是内置的自定义MyBatis拦截器,因代码量过多,为了保证不违反本博文文不对题的原则,此处不再做多余讲解,如有需要,我可以另行写一篇博客单独解释并讲解MyBatis拦截器的概念和原理,深度解析MyBatis源码

拓展

不仅有这几个参数,更还有参数等用以更进阶的分页查询定义,如需更深入的了解,我可以另行写一遍进阶PageHelper使用,此文只作为寻常开发使用讲解

四. 总结

PageHelper作为GitHub上现在近10K的开源分页框架,也许代码深度和广度不及主流市场框架和技术,虽然在功能的实现和原理上,造轮子的难度不高,源码也很清晰,但是在很大程度上解决了很多基于MyBatis的分页技术难题,简化并提示了广大开发者的效率,这才是开发者们在开发的路上应该向往并为之拼搏的方向和道路. 而我们作为受益者,也不应当仅仅是对其进行基本的使用,开发之余,我们也应该关注一些框架的拓展,对框架的底层有一定程度上的了解,并为之拓展和优化

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201026A01HV300?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码关注腾讯云开发者

领取腾讯云代金券