mybatis源码(五)springboot pagehelper实现查询分页

news2025/3/1 14:31:22

1、背景

springboot的pagehelper插件能够实现对mybatis查询的分页管理,而且在使用时只需要提前声明即可,不需要修改已有的查询语句。使用如下:

之前对这个功能一直很感兴趣,但是一直没完整看过,今天准备详细梳理下。按照个人的习惯,我喜欢带着问题去看源码,这次浏览源码我希望可以了解以下两个问题:
1)分页插件什么时候被加载的
2)分页插件什么时候生效的,如何生效的

2、技巧(可跳过)

mybatis自身的功能原理这里我们不深入探讨,网上文章很多,我之前也写过类似的文章,这里为了防止文章杂乱,我们只关注感兴趣的点,即mybatis pagehelper分页原理,其它的一笔带过。另外因为mybatis查询生效都是用的代理对象。如果对源码不是很熟悉,很难第一时间找到打断点的位置。不过通过查看page的源码可以知道,最后的分页参数都放到了threadlocal对象里,如下:




而最后使用该threadlocal时是一定会调用它的获取和移除方法,因此我们在它的这两个方法中加断点。等进入断点后再通过方法栈追踪其全流程。如下:

3、源码追踪流程

有了上述方法栈的查看,我们知道了查询的入口和大体流程,现在重新执行查询跟随源码脚步来详细看下分页的实现逻辑。首先是在代码中我们主动进行分页查询,如下:

前三行代码很简单,主要是从httpservletrequest中获取分页参数,并设置到当前线程的threadlocal变量中。第四行则是调用接口方法进行查询。在mybatis中,接口方法最终功能的实现主要是依靠MapperProxy代理类实现(代理类中包含接口和xml配置相关信息),所以接下来我们直接到MapperProxy中打断点追踪(至于mybatis如何关联接口和mapper.xml文件,MapperProxy如何生成等问题不是本文讨论重点,感兴趣的可以单独去查阅或者浏览我以前的相关文章)。

MapperProxy是代理对象,主要调用方法是invoke方法,所以在该方法中加断点,或者依靠第二步中的技巧查看整个查询的方法栈,随后再在MapperProxy对应的行中加断点。具体断点信息如下:

因为method不是Object类型,所以执行else里面的代码:

可以看到最后会执行PlainMethodInvoker对象里的invoke方法,所以我们接着到该方法中打断点并继续追踪查看:

可以看到调用SqlSessionTemplate对象的selectList方法,我们接着向下看:

再接着到DefaultSqlSession中继续查看:

可以看到,最后会调用CachingExecutor的query方法,但是这里要留意一点,那就是CachingExecutor是一个代理对象,执行代理对象方法首先要进入代理,并执行invoke方法。我们这里通过断点调试的步入查看执行哪个类的invoke方法:

可以看到最终是通过Plugin代理实现CachingExecutor对象query方法的调用。我们再接着向下看:

因为要执行的方法是查询方法,其是分页拦截器指定要拦截的方法类型,所以会进入拦截方法中。这里我标记了两个框,第一个是拦截,第二个是不进行拦截直接执行查询方法,因此可以推测分页逻辑是再拦截器中进行的。拦截器中会进行sql的改写,所以这里进入拦截器中进一步查看。拦截器对象为PageInterceptor到这终于和本文的主题关联上了,接下来我们到分页拦截器中看一下:

可以看到源码中都加好注释了,看起来就更简单了,这里我们看下进入分页的条件:

可以看到最后获取的page对象实际上就是我们一开在代码中传入分页参数创建的page对象。因为page对象不为空,所以会返回false,进而不跳过分页逻辑。(另外这里要提醒下各位小伙伴,page对象继承了ArrayList,所以断点查看时看不得page里面的内容,只能看到其size为0。)

我们接着回到主流程向下看,随后会判断是否需要计算总数,默认创建page时需要计算总数,这里我们就不进入before方法查看了,里面逻辑比较简单。下面我们简单看下计算总数的逻辑:

可以看到其计算总数的sql比较精简,主要是根据查询的对象和条件直接计算总数。这里sql的解析和生成主要是依赖jsqlparser工具类实现(jsqlparser的使用可以参考我以前的文章),sql解析比较复杂,感兴趣的可以自行查看。接着我们再回到主流程,看下何时添加的分页查询参数:

可以看到最后再sql末尾加了limit分页参数,而这个sql的改写过程与计算count类似,都是通过jsqlparser工具实现的。

通过上面的流程,我们已经知道了分页插件如何生效的了。但是还有一个问题是分页插件如何被加载的。这个流程比较简单,我也是通过第二步的技巧逆推的全流程。下面我按照正常项目的加载顺利简单介绍下:
当我们在pom中引入pageHelper插件依赖并且在yml中配置分页相关的信息时,项目启动后就会主动的进行插件的初始化并注入到插件拦截器链里面。大体逻辑如下:

可以看到分页插件有个配置类,其在项目初始化的时候会创建分页拦截器,并调用Configuration进行添加,接着我们看下最后会将拦截器添加到哪里:

可以看到最终会添加到拦截器链对象的私有集合里。但是我们最终使用拦截器是在Plugin对象里用的,而不是在拦截器链里面,那Plugin如何使用到该拦截器的呢?

拦截器链里有个pluginAll方法,它会封装拦截器成一个“链式”动态代理对象,代理类是Plugin,该方法会在创建Executor时执行(还记得前面源码里介绍这块的提醒吧,Executor是一个被动态代理的对象),通过pluginAll方法,将拦截器封装成链并将Executor放在链路最后一层

可以看到通过pluginAll方法将拦截器封装成了一个链,下面再看一下Executor的创建就完全清晰了:

至此,我们两个问题都再浏览源码的过程中清晰了。

4、总结

1)该文章主要是探究sringboot分页插件实现的原理,所以对于mapper.xml与接口方法的整合和mybatis代理对象如何实现查看没有细讲,但是这部分也是查询过程中核心的代码。

2)由PageInterceptor分页拦截器拦截指定的查询请求,然后在拦截方法中调用PageHelper中的方法对sql进行改写,最后再进行提交。

2)无论计算总数还是重写分页sql,都是通过jsqlparser工具实现的(为了使得文章主题清晰,这里没有介绍sql的改写过程,jsqlparser的使用可以浏览我以前的文章)

3)Plugin是一个链式动态代理对象,最后一个节点是Executor被代理对象,前面的节点是Interceptor被代理对象。

4)再Plugin.wrap方法中会提取出拦截器里的signature标签,并保存在每个Plugin链式代理对象中。在被代理对象执行对应方法时,如果plugin代理对象包含对应的signature集合则说明当前被代理对象是拦截器,如果不包含signature或者signature标签没有拦截当前方法,则直接执行当前方法。

参考文章:

5分钟!彻底搞懂MyBatis插件+PageHelper原理 - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1278030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阻抗匹配电阻原理及其应用

一、匹配电阻的作用 1、阻抗匹配 当信号频率比较高,上升沿比较陡时,电子信号经过阻抗不同的地方时也会产设反射。 PCB的单线阻抗一般会设计成50Ω,发射端阻抗一般是17到40,而接收端一般是MOS管的输入,阻抗是比较大的…

Linux ____04、文件内容查看(命令),网络配置(命令),软硬链接(命令)

文件内容查看,软硬链接 一、文件内容查看1、cat 由第一行开始显示文件内容,用来读文章,或者读取配置文件啊,都使用cat名2、tac 从最后一行开始显示,可以看出 tac 是 cat 的倒着写!3、显示的时候&#xff0c…

C++基础 -33- 单目运算符重载

单目运算符重载格式 a和a通过形参确定 data1 operator() {this->a;return *this; }data1 operator(int) {data1 temp*this;this->a;return temp; }举例使用单目运算符重载 #include "iostream"using namespace std;class data1 {public :int a;data1(int…

linux复习笔记06(小滴)

演练企业静态ip地址配置过程 我们有时候会发现,在使用虚拟机的时候,如果使用远程连接工具,我们会发现,有时候连接不上去,但是我们去用ifconfig去查看的时候,我们发现是ip地址换了。所以往往我们也需要去固…

p标签在div中居中

新建一个html文件,命名为test.html,用于讲解如何在css中让div中的p标签居中。 在test.html文件内,在div内,使用p标签创建一行文字,用于测试。 在test.html文件内,设置div标签的class属性为mydiv。 在…

python绘制箱线图boxplot——用于多组数据的比较, 异常值检测

python绘制箱线图boxplot——用于多组数据的比较, 异常值检测 介绍箱线图方法简介箱线图适用范围seaborn.boxplot箱图外观设置异常值marker形状、填充色、轮廓设置完整代码 如下matplotlib.pyplot常见参数介绍 本文系统详解利用python中seaborn.boxplot绘制箱图boxplot。seab…

解决报错:error: (-215:Assertion failed) inv_scale_x > 0 in function ‘cv::resize‘

需求背景 欲使用opencv的resize函数将图像沿着纵轴放大一倍,即原来的图像大小为(384, 512), 现在需要将图像放大为(768, 512)。 源码 import cv2 import numpy as np# 生成初始图像 img np.zeros((384, 512), dtypenp.uint8) img[172:212, 32:-32] 255 H, W …

前端处理后端返回的字典值

<template><div></div> </template><script> export default {data () {return {data: {10: 北京,110: 山东},optionsData: []}},methods: {tranFn() {console.log(data>>>, this.data)const arr []for (let i 0; i < Object.keys…

分析实现HarmonyOS中的Linux内核架构模式

在当今的科技领域&#xff0c;操作系统是各种智能设备运行的关键所在。而在这方面&#xff0c;华为的鸿蒙系统备受瞩目。那么&#xff0c;鸿蒙系统技术架构是怎样的呢&#xff1f;本文将为您揭开这一神秘面纱。 首先&#xff0c;我们需要了解鸿蒙系统的基本架构。鸿蒙系统采用…

优先队列详解

优先队列是计算机科学中的一种抽象数据类型&#xff0c;它是一种队列&#xff1a;元素拥有优先级&#xff0c;优先级最高的元素最先得到服务&#xff1b;优先级相同的元素按照在集合中的顺序得到服务。优先队列有两种主要的实现方法&#xff1a;堆和二叉搜索树。 简单来说&…

vcruntime140.dll无法继续执行代码五种解决方法修复教程

在电脑使用过程中&#xff0c;我们可能会遇到一些常见的错误提示&#xff0c;其中之一就是“vcruntime140.dll丢失”。这个错误通常会导致某些应用程序无法正常运行。本文将介绍vcruntime140.dll丢失对电脑的影响以及如何修复这个问题&#xff0c;并提供一些预防措施&#xff0…

TCP/IP_整理起因

先分享一个初级的问题&#xff1b;有个客户现场&#xff0c;终端设备使用客户网络更新很慢&#xff0c;使用手机热点更新速度符合预期&#xff1b;网络部署情况如下&#xff1a; 前期花费了很大的精力进行问题排查对比&#xff0c;怀疑是客户网络问题&#xff08;其他的客户现…

Redis缓存的使用

什么是缓存 缓存就是数据交换的缓冲区&#xff0c;是存储数据的临时地方&#xff0c;一般读写性能较高。 缓存的作用&#xff1a; 降低后端负载提高读写效率&#xff0c;降低响应时间 缓存的成本&#xff1a; 数据一致性成本代码维护成本运维成本 Redis特点 键值型数据库…

使用JSP+Servlet+MySQL实现登录注册功能

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

java使用poi读写excel(处理上下标和科学计数法)

Background 要读写如下图所示的excel&#xff0c;符号和单位中包含上下标&#xff0c;在读写时需要特殊处理&#xff1b;取值列中是科学计数法&#xff0c;读写时需要特殊处理&#xff1b;excel中包含多个sheet&#xff0c;读的时候把所有sheet的数据读出来&#xff0c;写的时候…

Azure Machine Learning - 使用.NET创建和管理AI搜索功能

本文介绍了如何在 Azure SDK for .NET 中使用 C# 和 Azure.Search.Documents客户端库来创建和管理搜索对象。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之linux存储管理(2)》(18)

《Linux操作系统原理分析之linux存储管理&#xff08;1&#xff09;》&#xff08;17&#xff09; 6 Linux存储管理6.2 选段符与段描述符6.2.1 选段符6.2.2 段描述符6.2.3 分段机制的存储保护 6.3 80x86 的分页机制6.3.180x86 的分页机制6.3.2 分页机制的地址转换6.3.3 页表目录…

uni-app 微信小程序 电子签名及签名图片翻转显示功能

文章目录 1. 需求背景2. 开始撸2.1 点击 重写 进入签名页面&#xff08;上图一&#xff09;2.2 书写签名&#xff0c;点击确认返回&#xff0c;及图片翻转显示&#xff08;上图二&#xff0c;三&#xff09; 3. 图片进行翻转&#xff0c;返回翻转后的图片 1. 需求背景 接的一个…

结构体||联合体

1.结构体 1.1实际生活中一些东西往往有多个元素组成。如一名学生有身高、体重、名字、学号等。这时候就需要用到结构体。 结构体是一些值的结合&#xff0c;这些值被称为成员变量。结构体的每个成员可以是不同类型的变量&#xff0c;如&#xff1a;标量、数组、指针、甚至是其…

锐捷EWEB网管系统 RCE漏洞复现

0x01 产品简介 锐捷网管系统是由北京锐捷数据时代科技有限公司开发的新一代基于云的网络管理软件&#xff0c;以“数据时代创新网管与信息安全”为口号&#xff0c;定位于终端安全、IT运营及企业服务化管理统一解决方案。 0x02 漏洞概述 Ruijie-EWEB 网管系统 flwo.control.ph…