【原创】提升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 &gt; #{startTime}</if><if test="endTime != null">and t.create_time &lt; #{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对象中拿到上次执行的分页数据,直接放入返回参即可。

小结

至此升级版分页插件和使用就此完成,上面代码其实也只是我自己项目的一小部分而已,起到的也只是一个抛砖引玉的作用,欢迎大家在评论区与我讨论交流,我会尝试将这个插件做得更好更加优雅。

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

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

相关文章

探索Selenium的规避检测策略

Selenium之规避检测 背景 ​ 目前很多大网站有对selenium采取了监测机制。在正常情况下我们用浏览器访问相关网站的window.navigator.webdriver的值为 undefined或者为false。而使用selenium访问则该值为true。我们如何伪装&#xff0c;防止被检测出来呢&#xff1f; ​ 这是…

POJ 3233 Matrix Power Series 动态规划(矩阵的幂)

一、题目大意 给出一个矩阵A&#xff0c; 输出矩阵B的每一项对M取余数的值。 二、解题思路 以二维矩阵为例&#xff0c;首先计算K2的情况&#xff0c;我们设结果矩阵为B 有如下表达式 那么不难看出&#xff0c;需要的矩阵其实就是以下的两个矩阵相乘后的左上角的N*N个 然后…

初识Linux——基本指令(详解)1

呀哈喽&#xff0c;我是结衣。 在学习数据结构的同时&#xff0c;也不要忘了Linux的学习啊。今天我们开始Linux的教学&#xff0c;在学习之前我们肯定要会搭建Linux的学习环境&#xff0c;在我们的以前的博客里是有讲解的&#xff0c;所以所以这里我们就不在多说&#xff0c;我…

UDP数据报套接字

文章目录 DatagramSocket APIDatagramPacket API示例一: 请求响应UDP服务端UDP客户端 DatagramSocket API Socket是操作系统中的一个概念&#xff0c;本质上是一种特殊的文件&#xff0c;Socket就属于把“网卡”这个设备给抽象成了文件。往 Socket 文件中写数据&#xff0c;就…

深入探讨Guava的缓存机制

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;今天咱们聊聊Google Guava的缓存机制。缓存在现代编程中的作用非常大&#xff0c;它能提高应用性能&#xff0c;减少数据库压力&#xff0c;简直就是性能优化的利器。而Guava提供的缓存功能&#xff0c;不仅强大…

iptables入门

今天我的工作遇到了巡检网络配置的任务&#xff0c;这次巡检的主机都是运行十多年的机器&#xff0c;并不是新的firewalld&#xff0c;基本都是iptables&#xff0c;上学的时候以为这些都没人用&#xff0c;所以没有认真学习&#xff0c;现在需要用到了&#xff0c;所以写一篇文…

PostGIS学习教程九:空间连接

PostGIS学习教程九&#xff1a;空间连接 空间连接&#xff08;spatial joins&#xff09;是空间数据库的主要组成部分&#xff0c;它们允许你使用空间关系作为连接键&#xff08;join key&#xff09;来连接来自不同数据表的信息。我们认为“标准GIS分析”的大部分内容可以表示…

17.认识下Docker之docker的核心原理(2)

1.容器-我的小世界 不知道大家看没看过小说《完美时间》&#xff0c;里面石昊经常进入一个小世界在里面与世隔绝的修炼或者战斗&#xff0c;总之就是在一个完全封闭的空间里做他想做的事情而与外界隔离&#xff0c;不受侵扰。通过前面的分析我们知道&#xff0c;Namepace让应用…

SringBoot的启动原理,保姆级带你认识,让面试管对你刮目相看

SringBoot的启动原理&#xff0c;保姆级带你认识&#xff0c;让面试管对你刮目相看 一&#xff0c;介绍 graph TD; A[启动类] --> B[SpringApplication.run()] B --> C[创建SpringApplication实例] C --> D[初始化应用上下文] D --> E[加载应用配置] E --> F[…

树_二叉搜索树累加求和

//给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 // node.val 的值之和。 // // 提醒一下&#xff0c;二叉搜索树满足下列约束…

【Vulnhub 靶场】【hacksudo: FOG】【简单 - 中等】【20210514】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/hacksudo-fog,697/ 靶场下载&#xff1a;https://download.vulnhub.com/hacksudo/hacksudo-FOG.zip 靶场难度&#xff1a;简单 - 中等 发布日期&#xff1a;2021年05月14日 文件大小&#xff1a;1.3 GB 靶场作…

解决 vite 中 import.meta.globEager is not function 的问题

本人正在重构两年前搭建到一半的博客网站&#xff0c;相关依赖都很陈旧&#xff0c;用到了 npm-check-updates 检测项目可升级依赖&#xff1a; 升级完成后解决完依赖发现控制台报错 import.meta.globEager is not function解决方案&#xff1a; vite版本降至4.3.0 亲测有效&…

忽略python运行出现的大量警告

添加以下代码即可 import warnings warnings.filterwarnings(ignore)

App内存优化

一、内存优化介绍 1.背景介绍 内存是大问题但缺乏关注压实骆驼的最后一个稻草&#xff08;堆栈溢出&#xff09; 2.内存问题 内存抖动&#xff1a;锯齿状、GC导致卡顿内存泄露&#xff1a;可用内存减少、频繁GC内存溢出&#xff1a;OOM&#xff0c;程序异常 二、优化工具选…

FreeRTOS简介

FreeRTOS简介 文章目录 FreeRTOS简介前言一、什么是FreeRTOS&#xff1f;二、FreeRTOS的特点三、FreeRTOS的版本和参考资料1、FreeRTOS版本2、FreeRTOS源码获取3、FreeRTOS参考资料 四、FreeRTOS源码简介 前言 FreeRTOS是一个可以基于ROM运行的、可裁剪的、抢占式、实时多任务…

Photoshop最新版PS2024安装使用 Ver25.0.0

Photoshop&#xff0c;这个是长红了几十年的软件&#xff0c;我大概从它的3.0版本开始用&#xff0c;目前已迭代到25.0&#xff0c;但一直还在用CS4/11.0版本&#xff0c;一直秉持着够用即可的原则&#xff0c;因为不是专业的平面设计人员&#xff0c;能够简单PP图片就行。&…

C++智能指针及简单实现

C智能指针 堆内存、栈内存与静态内存静态内存栈内存堆内存 动态内存管理new、delete运算符智能指针实现智能指针 shared_ptr智能指针的线程安全问题解决 unique_ptrweak_ptr循环引用 思维导图本模块思路 动态内存管理 - cppreference.com 堆内存、栈内存与静态内存 静态内存 …

视觉测量基础

1. 相机模型 1.1 坐标系转换原理 世界坐标系(world Coords):点在真实世界中的位置&#xff0c;描述相机位置。 相机坐标系(Cameras Coords):以相机光学系统中心&#xff08;镜头中心&#xff09;为原点&#xff0c;建立相机坐标系。 图像物理坐标系(Film Coords):经过小孔成…

微服务实战系列之J2Cache

前言 经过近几天陆续发布Cache系列博文&#xff0c;博主已对业界主流的缓存工具进行了基本介绍&#xff0c;当然也提到了一些基本技巧。相信各位盆友看见这么多Cache工具后&#xff0c;在选型上一定存在某些偏爱: A同学说&#xff1a;不管业务千变万化&#xff0c;我对Redis的…

企业如何制定精准营销策略?

在当今的数字化时代&#xff0c;位置数据已经成为企业营销策略中不可或缺的一部分。通过收集和分析客户的位置数据&#xff0c;企业可以更好地了解客户的行为和需求&#xff0c;制定更精准的营销策略&#xff0c;从而提高营销效率。 首先&#xff0c;利用IP地址位置数据可以帮助…