mybatis源码(五)springboot pagehelper实现查询分页
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原理 - 知乎
相关文章:
mybatis源码(五)springboot pagehelper实现查询分页
1、背景 springboot的pagehelper插件能够实现对mybatis查询的分页管理,而且在使用时只需要提前声明即可,不需要修改已有的查询语句。使用如下: 之前对这个功能一直很感兴趣,但是一直没完整看过,今天准备详细梳理下。按…...
【BUG】SpringBoot项目Long类型数据返回前端精度丢失问题
问题描述 后端再给前端返回数据,使用Long类型的时候存在精度丢失问题。 原因分析: 分布式项目中广泛使用雪花算法生成ID作为数据库表的主键,Long类型的雪花ID有19位,而前端接收Long类型用的是number类型,但是number…...
UI自动化Selenium find_elements和find_element的区别
# 如果获取的element是list,那么需要用find_elements方法;此方法会返回list,然后使用len() 方法,计算对象的个数; # find_element方法返回的不是list对象,所以导致没办法计算对象个数 # 1.返回值类型不同…...
【Android】Window和WindowManager
文章目录 理解Window和WindowManagerWindow和WindowManagerWindow的内部机制Window的添加过程Window的删除过程Window的更新过程 Window的创建过程Activity的Window创建过程Dialog的Window创建过程Toast的Window创建过程 理解Window和WindowManager Window是一个抽象类…...
如何解决 Python ModuleNotFoundError 错误
模块对于开发 Python 程序很重要。 使用模块,我们可以分离代码库的不同部分以便于管理。 使用模块时,了解它们的工作方式以及如何将它们导入我们的代码非常重要。 如果没有这种理解或错误,我们可能会遇到不同的错误。 此类错误的一个示例是…...
Day62.算法训练
718. 最长重复子数组 class Solution {public int findLength(int[] nums1, int[] nums2) {int max 0;int[][] dp new int[nums1.length][nums2.length];for (int i 0; i < nums1.length; i) {for (int j 0; j < nums2.length; j) {if (nums1[i] nums2[j]) {if (i …...
Linux smbd命令教程:如何配置和管理Samba服务器(附案例详解和注意事项)
Linux smbd命令介绍 smbd是Samba套件的一部分。smbd是一个服务器守护进程,为Windows客户端提供文件共享和打印服务。服务器使用SMB(或CIFS)协议为客户端提供文件空间和打印服务。这与LanManager协议兼容,可以为LanManager客户端提…...
音视频学习(十九)——rtsp收流(tcp方式)
前言 本文主要介绍以tcp方式实现rtsp拉流。 流程图 流程说明: 客户端发起tcp请求,如向真实相机设备请求,端口一般默认554;tcp连接成功,客户端与服务端开始rtsp信令交互;客户端收到play命令响应后,开启线…...
LangChain(0.0.340)官方文档三:Prompts上——自定义提示模板、使用实时特征或少量示例创建提示模板
文章目录 一、 Prompt templates1.1 langchain_core.prompts1.2 PromptTemplate1.2.1 简介1.2.2 ICEL1.2.3 Validate template 1.3 ChatPromptTemplate1.3.1 使用role创建1.3.2 使用MessagePromptTemplate创建1.3.3 自定义MessagePromptTemplate1.3.3.1 自定义消息角色名1.3.3.…...
【算法】合并K个升序链表
这道题主要考察的是归并排序,因为已经升序过了,更好理解了。 当然也可以采用分治的思路;或采用最小堆的思路;面试中校招同学写出一种即可,如果能全概览讲一下,就更加分了。 #############################…...
持续集成交付CICD:GitLab Webhook触发Jenkins流水线
目录 一、实验 1.Jenkins远程下载GiaLab仓库代码 2.curl远程触发Jenkins流水线 3.GitLab Webhook触发Jenkins流水线 二、问题 1.GitLab配置Webhook时报错 一、实验 1.Jenkins远程下载GiaLab仓库代码 (1) Jenkins添加选项参数 (2)添加字符参数 (3)查看构建参数情况 (4)添…...
计算机网络测试题
一 单项选择题(5分) 1、假设要发送的数据为101110,采用CRC的生成多项式是X31,试求应添加在数据后面的余数。(5分) 110 011(答案) 101 001 实际得分:5分 二 填空题(95分) 1、以下3个子地址块…...
vscode如何在没有网络的情况下安装插件
vscode如何在没有网络的情况下安装插件 start 遇到没有网络的电脑,无法直接从插件市场安装vscode的插件。写一下 vscode 插件离线安装的方法. 解决方案 目标电脑没有可以安装插件的网络,那我们只能在有网络的环境下载好我们的插件。然后拷贝软件到无…...
自定义类型:结构体、联合、枚举
目录 一、⾃定义类型:结构体 1.结构体类型 1. 1结构体类型的声明 结构体变量的创建和初始化 1.2 结构的特殊声明 1.3 结构的自引用 2. 结构体内存对齐 ①:对齐规则 ②:offsetof函数 ③:为什么存在内存对⻬? ④ 修改默认对⻬…...
HelpLook可以作为wordpress的替代品,帮助企业快速搭建博客
博客作为一个非常有价值的平台,在当今的数字时代具有重要的意义。对于个人和企业来说,选择一款适合自己需求的专业博客搭建软件至关重要。本篇文章将会通过对比两个专业的博客搭建软件——HelpLook和WordPress,看看为什么我说HelpLook可以作为…...
单片机实现数码管动态显示
动态显示的特点是将所有位数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。这样一来,就没有必要每一位数码管配一个锁存器,从而大大地简化了硬件电路。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码…...
jbrowse基因组浏览器部署
一、jbrowse部署 1.1 jbrowse部署 sudo mkdir /var/www/jbrowse; sudo chown whoami /var/www/jbrowse; # cd into it cd /var/www/jbrowse;1.2 nginx配置 server {listen 80 default_server;listen [::]:80 default_server;server_name _;#root /var/w…...
神经网络 模型表示(一)
神经网络 模型表示 模型表示一 为了构建神经网络模型,我们需要首先思考大脑中的神经网络是怎样的?每一个神经元都可以被认为是一个处理单元/神经核(processing unit/Nucleus),它含有许多输入/树突(input/…...
【漏洞复现】智跃人力资源管理系统GenerateEntityFromTable.aspx接口存在SQL注入漏洞 附POC
漏洞描述 智跃人力资源管理系统是基于B/S网页端广域网平台,一套考勤系统即可对全国各地多个分公司进行统一管控,成本更低。信息共享更快。跨平台,跨电子设备。智跃人力资源管理系统GenerateEntityFromTable.aspx接口处存在SQL注入漏洞,攻击者可通过该漏洞获取数据库中的信…...
【matlab程序】画海洋流场
【matlab程序】画海洋流场 clear;clc; file ( ‘0227.nc’); latncread(file,‘latitude’); lonncread(file,‘longitude’); uncread(file,‘water_u’); vncread(file,‘water_v’); [x,y]meshgrid(lon,lat); xx’; yy’; interval4; figure (1) set(gcf,‘color’,[1 1 1…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
