【原创】提升MybatisPlus分页便捷性,制作一个属于自己的分页插件,让代码更加优雅
前言
MybatisPlus的分页插件有一点非常不好,就是要传入一个IPage,别看这个IPage没什么大不了的,最多多写一两行代码,可这带来一个问题,即使用xml的查询没法直接取对象里面变量的值了,得@Param指定xml中的变量名才行,得写#{search.name},而不是#{name},这也太不优雅了!可以说是相当的不优雅!
我之前就想过一些办法解决这个问题,比如使用PageHelper,这玩意更不省心,Page只会被第一次查询消费,而在我项目中,分页有一大堆的前置查询,比如:权限查询(最多),是否存在类查询(较多),以及其他一些前置查询业务。这往往会使得PageHelper被提前消费,列表依旧返回所有内容,这问题经常让人猝不及防,让程序猿苦不堪言。因此PageHelper方案也被我放弃了,最终还是打算自己实现一个分页插件,替换MP自己的分页插件。
设计思路
作为一名曾经的Android前端程序猿,Context模式对我来说再熟悉不过了,可以说是形影不离,即将几乎所有页面要用到的信息都放置到Context(上下文)中,那我对于后端请求来说不也可以这么做吗?将所有接口请求以及过程相关信息放到Context创建的对象中,对象放到线程中,随用随取,只要拿到Context意味着拿到了一切,跟Android的Context一样!当然这玩意必须结合MP的分页插件和PageHelper的优点,避免其自身的缺陷。
效果展示
图上为Kotlin代码(Android程序猿必备),实现分页仅需2行,
第一行:开启分页,说明下一个请求是需要执行分页的
第二行:进行查询,结果返回的只是一个List!分页信息呢?全保存在Context对象中了。
返回结果如上图所示,为了节约服务器带宽,我这边的返回参数全部使用单个字母表示,其中p就是page信息,pn:pageNum,ps:pageSize,tc:totalCount,tp:totalPage
当然这玩意和PageHelper一样,只能负责一次分页查询,当然一个接口也只需要一次分页查询, 不服来辩!
直接上代码
代码分为前中后三个部分
前期:准备Context
准备Context阶段我是在Aspect中进行的,切面为Controller方法,在执行Controller方法前,初始化一个Context对象并将其放到map中,Key为当前Thread对象,Value为Context,这里的代码过于复杂,且涉及到token校验,这里我就不放完整的出来了,以免我的服务器遭到攻击。
val context = Context()val thread = Thread.currentThread()threadContextMap[thread] = context
反正大概就这意思,Context中当然也包含了所有入参信息,包括了pageNum、pageSize、totalCount、totalPage等等。
中期:准备xml、分页插件
由于项目中大量查询都是基于xml的,包含很多子查询和join查询,不可能都用QueryWrapper查询,因此xml的简洁化是必须的。我这里用的示例查询xml为:
<select id="findByList" resultType="com.itdct.server.admin.example.vo.ExampleListVo">select t.* from test_example as t<where><if test="name != null and name != ''">and t.name = #{name}</if><if test="number != null">and t.number = #{number}</if><if test="keyword != null and keyword != ''">and t.name like concat('%',#{keyword},'%')</if><if test="startTime != null">and t.create_time > #{startTime}</if><if test="endTime != null">and t.create_time < #{endTime}</if></where><if test="orderBy == null">order by t.create_time desc</if><if test="orderBy != null">order by ${orderBy}</if></select>
查询的Mapper为:
fun findByList(query: ExampleQo): List<ExampleListVo>
可以发现查询方法不包含任何@Param,<if>中的变量也没有xxx.fieldName,甚至用ctrl+左键点击#{变量}还能跳转到类中相应的成员变量,这就是我想要实现的效果。
然后就是分页插件了,这个插件我还是基于原来的MP的分页插件,只需要对其进行稍加修改即可为我所用。
package com.itdct.server.admin.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect;
import com.itdct.server.common.dto.Context;import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.sql.SQLException;
import java.util.List;
import java.util.Map;/*** @author DCT* @version 1.0* @date 2023/11/10 14:53:24* @description*/
public class ContextPaginationInnerInterceptor extends PaginationInnerInterceptor {protected Map<Thread, Context> threadContextMap;public ContextPaginationInnerInterceptor(DbType dbType) {super(dbType);}public ContextPaginationInnerInterceptor(IDialect dialect) {super(dialect);}public ContextPaginationInnerInterceptor(DbType dbType, Map<Thread, Context> threadContextMap) {super(dbType);this.threadContextMap = threadContextMap;}@Overridepublic boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {// INFO: DCT: 2023/12/5 获取到当前线程的上下文对象Context context = threadContextMap.get(Thread.currentThread());if (context == null) {return true;}// INFO: DCT: 2023/12/5 不启动分页直接跳过boolean startPage = context.isStartPage();if (!startPage) {return true;}// INFO: DCT: 2023/12/5 这个page就是MP的分页Page Page page = context.getPage();if (page == null) {return true;}long size = page.getSize();if (size < 0) {return true;}// INFO: DCT: 2023/12/5 以下为原来的MP分页插件代码 BoundSql countSql;MappedStatement countMs = buildCountMappedStatement(ms, page.countId());if (countMs != null) {countSql = countMs.getBoundSql(parameter);} else {countMs = buildAutoCountMappedStatement(ms);String countSqlStr = autoCountSql(page, boundSql.getSql());PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());}CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);List<Object> result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql);long total = 0;if (CollectionUtils.isNotEmpty(result)) {// 个别数据库 count 没数据不会返回 0Object o = result.get(0);if (o != null) {total = Long.parseLong(o.toString());}}page.setTotal(total);long totalPage = total / page.getSize();if (total % page.getSize() != 0) {totalPage++;}page.setPages(totalPage);return continuePage(page);}@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Context context = threadContextMap.get(Thread.currentThread());if (context == null) {return;}boolean startPage = context.isStartPage();if (!startPage) {return;}// INFO: DCT: 2023/12/5 这个page就是MP的分页Page Page page = context.getPage();if (page == null) {return;}long size = page.getSize();if (size < 0) {return;}// 处理 orderBy 拼接boolean addOrdered = false;String buildSql = boundSql.getSql();List<OrderItem> orders = page.orders();if (CollectionUtils.isNotEmpty(orders)) {addOrdered = true;buildSql = this.concatOrderBy(buildSql, orders);}// size 小于 0 且不限制返回值则不构造分页sqlLong _limit = page.maxLimit() != null ? page.maxLimit() : maxLimit;if (page.getSize() < 0 && null == _limit) {if (addOrdered) {PluginUtils.mpBoundSql(boundSql).sql(buildSql);}return;}handlerLimit(page, _limit);IDialect dialect = findIDialect(executor);final Configuration configuration = ms.getConfiguration();DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);List<ParameterMapping> mappings = mpBoundSql.parameterMappings();Map<String, Object> additionalParameter = mpBoundSql.additionalParameters();model.consumers(mappings, configuration, additionalParameter);mpBoundSql.sql(model.getDialectSql());mpBoundSql.parameterMappings(mappings);// INFO: DCT: 2023/12/5 利用完后置为false context.setStartPage(false);}
}
完整代码如上面所示,其中绝大部分都是MP原来的分页插件里的代码,我只是对其稍加修改而已。
后期:返回给前端
有了Context对象真的可以为所欲为哦,successPage方法如下:
public <T> RespPageVo<T> successPage(List<T> pageData) {Context context = getContext();Page page = context.getPage();if (page != null) {return new RespPageVo<T>(pageData, page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());} else {log.warn("page is null!");return new RespPageVo<T>(pageData, 0L, 0L, 0L, 0L);}}public Context getContext() {Context context = threadContextMap.get(Thread.currentThread());return context;}
处于BaseService的代码还是Java写的,没有全面Kotlin化,由于Context对象中存有MP的Page对象,因此可以直接从Page对象中拿到上次执行的分页数据,直接放入返回参即可。
小结
至此升级版分页插件和使用就此完成,上面代码其实也只是我自己项目的一小部分而已,起到的也只是一个抛砖引玉的作用,欢迎大家在评论区与我讨论交流,我会尝试将这个插件做得更好更加优雅。
相关文章:
【原创】提升MybatisPlus分页便捷性,制作一个属于自己的分页插件,让代码更加优雅
前言 MybatisPlus的分页插件有一点非常不好,就是要传入一个IPage,别看这个IPage没什么大不了的,最多多写一两行代码,可这带来一个问题,即使用xml的查询没法直接取对象里面变量的值了,得Param指定xml中的变…...
pythonselenium自动化测试实战项目
说明:本项目采用流程控制思想,未引用unittest&pytest等单元测试框架 一.项目介绍 目的 测试某官方网站登录功能模块可以正常使用 用例 1.输入格式正确的用户名和正确的密码,验证是否登录成功; 2.输入格式正确的用户名和不…...
智能优化算法应用:基于瞬态优化算法无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于瞬态优化算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于瞬态优化算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.瞬态优化算法4.实验参数设定5.算法结果6.参考…...
springMVC 三大组件解析
springMVC组件概述 DispatcherServlet(调度器Servlet): DispatcherServlet 是 Spring MVC 的前端控制器(Front Controller)。它负责接收来自客户端的请求,然后将请求分发给相应的处理器(Control…...
聊聊nginx的keepalive_time参数
序 本文主要研究一下nginx的keepalive_time参数 keepalive_time Syntax: keepalive_time time; Default: keepalive_time 1h; Context: http, server, location This directive appeared in version 1.19.10.nginx的1.19.10版本新增了keepalive_time参数,用于限…...
沐风老师3DMAX键盘球建模方法详解
3DMAX键盘球建模教程 本教程给大家分享一个3dMax键盘球的建模方法过程。在学习本教程之前,大家需要对3dMax基本操作及建模知识有所掌握,还是那句话:做实例的前提是选学习基础知识和掌握3dMax的基本操作。 下面就给大家一步一步讲解演示3dMax…...
算法通关村第一关—白银挑战—链表高频面试算法题—查找两个链表的第一个公共子节点
文章目录 查找两个链表的第一个公共子节点(1)暴力求解法(2)使用哈希Hash⭐(3)使用集合⭐ - 与Hash类似(4)使用栈⭐(5)仍有更多方法,作者尚未理解&…...
C/C++ 发送与接收HTTP/S请求
HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的协议。它是一种无状态的、应用层的协议,用于在计算机之间传输超文本文档,通常在 Web 浏览器和 Web 服务器之间进行数据通信。HTTP 是由互联网工程任务组(IETF…...
【算法集训】基础数据结构:一、顺序表(下)
由于今天的题目是昨天剩下的,所以只有两道题,也非常简单,刷完下班~~~嘿嘿 第六题 2656. K 个元素的最大和 https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/description/ 很简单的思路,要得到得分最大的&…...
[Java][项目][战斗逻辑]基于JFrame的文字游戏
项目注解: Core:启动文件 AttributeBean:玩家属性 BackpackedBean:背包设计(未完成) BackpackedFrame:背包页面(未完成) BattleField:战斗逻辑(核心&…...
顺序表和链表面试题
文章目录 顺序表(1)原地移除数组中所有的元素val,要求时间复杂度为O(N),空间复杂度为O(1)。(2)删除有序数组中的重复项(3)合并两个有序数组 链表(1)删除链表中等于给定值 val 的所有节点(2)反转一个单链表(3) 合并两个有序链表(4)链表的中间结点(5)链表中…...
树_二叉搜索树累加求和
//给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 // node.val 的值之和。 // // 提醒一下,二叉搜索树满足下列约束…...
gcc编译流程概述
前言 本篇文章介绍gcc编译器编译C文件的流程概述 比如我们创建了一个.c文件hello_gcc.c #include <stdio.h> int main() {printf("Hello gcc!!!\n");return 0; }最简单的方式就是在终端使用命令 gcc hello_gcc.c -o hello_gcc // 编译、汇编、链接 ./hello_…...
【web安全】ssrf漏洞的原理与使用
前言 菜某对ssrf漏洞的总结。 ssrf的作用 主要作用:访问外界无法访问的内网进行信息收集。 1.进行端口扫描,资源访问 2.指纹信息识别,访问相应的默认文件 3.利用漏洞或者和payload进一步运行其他程序 4.get类型漏洞利用,传参数…...
佳易王会员管理软件店铺积分以及积分兑换系统
一、佳易王会员管理软件大众版 部分功能简介: 1、会员信息登记 :可以直接使用手机号登记,也可以使用实体卡片,推荐用手机号即可。 2、会员卡类型 :可以自由设置卡的类型,比如:充值卡、计次卡、…...
Django回顾【二】
目录 一、Web框架 二、WSGI协议 三、 Django框架 1、MVC与MTV模型 2、Django的下载与使用 补充 3、启动django项目 补充 5、 Django请求生命周期 四、路由控制 1、路由是什么? 2、如何使用 3、path详细使用 4、re_path详细使用 5、反向解析 6、路由…...
[Ubuntu 18.04] RK3399搭建SSH服务实现远程访问
SSH(Secure Shell)是一种网络协议和软件,用于安全地远程登录到计算机并进行网络服务的加密通信。它提供了加密的认证和安全的数据传输,使得在不安全的网络中进行远程管理和访问变得更加安全。 以下是 SSH 服务的一些关键特点和用途: 安全认证:SSH 使用公钥/私钥加密技术…...
Linux进程间通信之共享内存
📟作者主页:慢热的陕西人 🌴专栏链接:Linux 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客主要内容讲解共享内存原理和相关接口的介绍,以及一个…...
lv11 嵌入式开发 RTC 17
目录 1 RTC简介 编辑2 Exynos4412下的RTC控制器 2.1 概述 2.2 特征 2.3 功能框图 3 寄存器介绍 3.1 概述 3.2 BCD格式的年月日寄存器 3.3 INTP中断挂起寄存器 3.4 RTCCON控制寄存器 3.5 CURTICCNT 作为嘀嗒定时器使用的寄存器 4 RTC编程 5 练习 1 RTC简介 RTC(…...
c语言指针详解(上)
目录 一、指针的基本概念和用法 二、指针运算 2.1 指针的自增和自减运算 2.2 指针的自增和自减运算 三、数组和指针 四、指针和函数 4.1 在函数中使用指针作为参数和返回值 4.1.1 使用指针作为函数参数 4.1.2 使用指针作为函数返回值 4.2 指针参数的传值和传引用特性 4.2.1 指针…...
如何删除mac苹果电脑上面的流氓软件?
在使用苹果电脑的过程中,有时候我们也会遇到一些不需要的软件。无论是因为不再需要,或者是为了释放磁盘空间,删除这些软件是很重要的。本文将为大家介绍怎样删除苹果电脑上的软件! CleanMyMac X全新版下载如下: https://wm.make…...
WordPress(11)给文章添加预计阅读时长
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、文件配置二、代码块1.引入库2.配置 single.php三、效果图前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了…...
周周爱学习之快速排序
快速排序,顾名思义,快速排序是一种速度非常快的一种排序算法 平均时间复杂度为O(),最坏时间复杂度为O()数据量较大时,优势非常明显属于不稳定排序 1.算法描述 每一轮排序选择一个基准点(pivot)进行分区 让小于基准点…...
国产接口测试工具APIpost
说实话,了解APIpost是因为,我的所有接口相关的文章下,都有该APIpost水军的评论,无非就是APIpost是中文版的postman,有多么多么好用,虽然咱也还不是什么啥网红,但是不知会一声就乱在评论区打广告…...
MySQL电商管理系统练习题及答案
一 、表结构 用户表(user):id(主键)、username、password、email、phone、age商品表(product):id(主键)、name、price、stock、description订单表(order):id(主键)、user_id(外键,关联用户表)、total_price、status、create_time…...
每日3道PWN(第二天)
ciscn_2019_n_1 参考: [BUUCTF-pwn]——ciscn_2019_n_1-CSDN博客 [BUUCTF]PWN5——ciscn_2019_n_1_ciscn_2019_n_4-CSDN博客 BUUCTF—ciscn_2019_n_1 1-CSDN博客 checksec一下 64位栈溢出 按f5查看main函数,双击可疑函数 发现含有命令执行的且发现fl…...
SAP STMS传输请求
一、概述 一般SAP项目上都会有六套系统,分别是: 测试环境-DEV系统 主要由100:沙盘系统:用于业务顾问配置 200:开发系统:用于开发ABAP写代码 300:测试系统:主要是单元测试、顾问自己…...
L1-009:N个数求和
目录 ⭐题目描述⭐ ⭐分析 ⭐程序代码 运行结果 ⭐文案分享⭐ ⭐题目描述⭐ 本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数分子/分母的形式给出的,你输出的和也必须是有理数的形式。 输入格式: 输入第一行给出…...
当发送“Hello,World”时,channel发生了什么?
一、Netty概述 1.Netty是什么? Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。 2.Netty的地位怎么样? Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE …...
服务器运行情况及线上排查问题常用命令
一、top命令 指令行: top返回: 返回分为两部分 (一)系统概览,见图知意 以下是几个需要注意的参数 1、load average: 系统负载,即任务队列的平均长度。三个数值分别为 1分钟、5分钟、15分…...
中铁建设集团有限公司官方网站/百度指数的基本功能
因为虽然匿名内部类在方法的内部,但实际编译的时候,内部类编译成Outer.Inner,这说明内部类所处的位置和外部类中的方法处在同一个等级上,外部类中的方法中的变量或参数只是方法的局部变量,这些变量或参数的作用域只在这个方法内部…...
电商网站开源授权二次开发/西安seo顾问公司
1. 概念:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库 * JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可…...
宁波网站seo报价/软文发稿网
1 摘要 本报告是在学习斯坦福大学机器学习课程前四节加上配套的讲义后的总结与认识。前四节主要讲述了回归问题,回归属于有监督学习中的一种方法。该方法的核心思想是从连续型统计数据中得到数学模型,然后将该数学模型用于预测或者分类。该方法处理的数据…...
手机端网站开发源码/百度提升排名
我们知道,pyecharts是个非常好的python画图工具包,但是在notebook使用的时候,会遇到图形没法显示的情况,这时候需要做如下设置:#在程序中添加一下代码,解决pecharts在notebook的依赖from pyecharts.globals…...
怎么通过网站打广告/网站制作公司哪家好
简介: puppet是一种Linux、Unix平台的集中配置管理系统,使用ruby语言,可管理配置文件、用户、cron任务、软件包、系统服务等。puppet把这些系统实体称之为资源,puppet的设计目标是简化对这些资源的管理 以及妥善处理资源间的依赖关…...
小学网站建设方案/营销网址
最近发现在Excel数据中的某一列存在日期和字母混合类型的数据时,C#读取到的Excel的数据将非日期类型的数据丢失了,读到的全部为“null”值,这个问题纠结了半天,在网上找到了解决方法。 方法一: 更新Excel的连接字符串&…...