老司机开发技巧,如何扩展三方包功能

news2024/11/25 14:42:29

前言

最近碰上有个业务,查询的sql如下:

 

sql

复制代码

select * from table where (sku_id,batch_no) in ((#{skuId},#{batchNo}),...);

本来也没什么,很简单常见的一种sql。
问题是我们使用的是mybatis-plus,然后写的时候有没有考虑到后面的查询条件,这里用的是mybatis-plus lambda的方式。
示例如下:

 

java

复制代码

LambdaQueryChainWrapper<Table> query = tableService.lambdaQuery(); query.eq(Table::getId, param.getId());

但是mysql-plus并没有支持这种sql的形式,要么用apply方法自定义拼接sql,要么不采用lambda方式,将语句写成xml形式。
不过,第一种方式又感觉很low,一大段java代码里插入一段sql字符串,看上去就很别扭,因为有点代码洁癖,只能果断放弃。
第二种改动又太大,前面那么多查询条件,又要全部移入xml里面,同时,本来已经通过测试的筛选,又要重新来一遍,太懒,实在干不动。
于是,又想想这么常见的场景,网上理应有解决方案。但不知道是搜索关键字不对还是确实没有,搜了半天没搜出来。

整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记的【点击此处即可】即可免费获取

无奈,只有尝试自己扩展一下。

扩展

扩展第三方包功能,这种事老司机应该已经轻车熟路了,可以划走了。我分享下自己的思路,这里以mybatis-plus lambda为例,其他的任何第三方包其实都一样。
首先应该明白一点,一个较为成熟的第三方包,里面的代码逻辑不亚于我们任何一个系统的代码逻辑。
所以如果平时我们开发中,想要去理清五六成的代码逻辑再动手,那领导自然不是很高兴(费时费力)。
其实,最好最快的方式,就是看看我们需要使用的类或方法,在第三方包里面的他们的实现思路是什么。
这里,我想要的结果,就是LambdaQueryChainWrapper这个类下有一个方法支持(sku_id,batch_no) in ((#{skuId},#{batchNo}),...)这种条件查询。
那么,我们找一个与这个方法相近的in方法,看看这里面的实现是怎么样的。 点进来:

image.png

进的是mybtisplus包下面func的一个接口。
先看看in的实现类有哪些

image.png

看上去LambdaQueryChainWrapper并没有直接实现这个接口。别急,这种一看就是这两个类肯定是LambdaQueryChainWrapper的父类。我们不急着点进去,先记下这两个类。
为什么不急着点进去看看这两个类怎么实现的in方法呢?
因为大部分情况,这里点出来的实现类布置一两个,大部情况可能十几二十个。从上层一个找,那么可能几天都看不完,而现在要做的,是从下网上找。
我们找到LambdaQueryChainWrapper这个类

image.png

它在mybatisplus的扩展包里。这里很简单,LambdaQueryChainWrapper继承自AbstractChainWrapper,运气很好,是我们刚才看见的in实现类的其中一个类。
为什么说运气很好?
因为这里还会存在的情况是,LambdaQueryChainWrapper并不直接继承in方法的实现类,而是它的父类,甚至父类的父类,四五层的父类才实现的in的方法。这种情况就需要一层层往上找,找到上面实现in方法的具体继承下来的类。
好了,看一下AbstractChainWrapper类中in方法的实现

image.png

还是比较简单,就两行。我们关注下这行

 

java

复制代码

getWrapper().in(condition, column, coll);

getWrapper()方法返回了一个AbstractWrapper对象。
可以先不用关注AbstractWrapper干了什么,我们感兴趣的是它执行了in方法。

image.png

AbstractWrapper类中实现的in方法具体实现在doIt方法中

image.png

看上去也没什么需要关注的点

 

java

复制代码

expression.add(sqlSegments);

看看add方法里面在干什么

image.png

嗯?已经在处理具体的sql片段了。从这里能看出来,大概意思orderBy、groupBy、having会加入特定集合,其他的会加入normal集合。
看到这就应该刹住车了,为什么?
因为既然这里没有看到将lambda处理成sql字符串的逻辑,而且很明显是准备做字符串拼接前的准备了。那么我们想要的将lambda方法处理成sql字符串的操作就是在前面执行的。
我们往前翻一翻

image.png

这里我们还漏了点东西。三个参数,condition不用去关注,这里一想就能明白,就是是否执行语句的标识。第二个参数是一个执行方法,看名字就能看出,是将字段转换成字符串的,点进去看看。

image.png

没什么复杂逻辑,再看看inExpression方法

image.png

嗯?看上去是不是有那么点意思了

image.png

image.png

formatSql的具体实现,就是把{0}替换成集合中具体的值。然后在inExpression方法返回时拼接成(值1,值2...)的形式。
到这里我们就能看明白,inExpression方法其实就是拼接的id in (id1,id2...)的条件。 那么,我们再来看看in的方法

image.png

结构是不是就是 字段 in (查询条件),和我们实际的sql基本接近。
那么到这里其实就已经有了大概的实现思路了。

实现思路

思路其实也比较简单,我们可以按照AbstractWrapper类中in方法的实现,实现我们想要的效果。
首先,新增一个类,参考LambdaQueryChainWrapper类,继承自AbstractChainWrapper。
为什么没有继承LambdaQueryChainWrapper?

image.png

因为字段的泛型在AbstractWrapper上,我们也需要这个泛型,用于传参。
但为了保证select功能可用,我们需要把LambdaQueryChainWrapper中的方法全部copy一份到我们新的类上。然后我们来构建我们需要的方法:

image.png

copy一份AbstractWrapper中in的实现,改巴改巴。我们需要传两个字段,同时需要两个集合参数。
为什么这里不用不定参数?
因为不定参只能在方法参数最后,这里用不定参就是字段和数据集合两个不定参,不好区分。
好了,现在的问题是,这里的几个方法在本类并没有被执行,就像图上所示红色的方法。
从原AbstractWrapper类中的实现我们可以看到,修饰这些方法的private和prodtected,但我们的实现类是在我们自己的包里的。所以并不能访问到原包下的方法。
每一个方法都copy一份到我们自己的包下?
当然这是不可取的,方法一个套一个,如果稍不注意就会遗漏或被修改,都容易出问题。
这里,比较常见的做法就是利用反射机制。
比如:

image.png

利用反射直接调用原实现类的方法,有一个小点是大部分方法是受保护的,所以需要执行ReflectionUtils.makeAccessible(method)
然后,我们来看下需要具体改造的点。
首先,字段需要拼接成(字段名1, 字段名2)的形式,这部分很简单,参考AbstractWrapper实现类,我们这样拼接

 

java

复制代码

StringPool.LEFT_BRACKET + columnsToString(true, column1, column2) + StringPool.RIGHT_BRACKET

另一个点就是拼接后面的参数,需要改成((参数值1,参数值2),(参数值1,参数值2)...)这种方式
虽然会费点功夫,但也基本好搞,这里直接上完整代码

 

java

复制代码

public class CustomerQueryChainWrapper<T> extends AbstractChainWrapper<T, SFunction<T, ?>, CustomerQueryChainWrapper<T>, LambdaQueryWrapper<T>> implements ChainQuery<T>, Query<CustomerQueryChainWrapper<T>, T, SFunction<T, ?>> { private final BaseMapper<T> baseMapper; public CustomerQueryChainWrapper(BaseMapper<T> baseMapper) { super(); this.baseMapper = baseMapper; super.wrapperChildren = new LambdaQueryWrapper<>(); } @SafeVarargs @Override public final CustomerQueryChainWrapper<T> select(SFunction<T, ?>... columns) { wrapperChildren.select(columns); return typedThis; } @Override public CustomerQueryChainWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) { wrapperChildren.select(entityClass, predicate); return typedThis; } @Override public String getSqlSelect() { throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getSqlSelect"); } @Override public BaseMapper<T> getBaseMapper() { return baseMapper; } public LambdaQueryWrapper<T> inOfParam2(SFunction<T, ?> column1, SFunction<T, ?> column2, List<?> coll1, List<?> coll2) { return doIt(true, () -> StringPool.LEFT_BRACKET + columnsToString(true, column1, column2) + StringPool.RIGHT_BRACKET, IN, inExpressionOfParam(coll1, coll2)); } public LambdaQueryWrapper<T> inOfParam3(SFunction<T, ?> column1, SFunction<T, ?> column2, SFunction<T, ?> column3, List<?> coll1, List<?> coll2, List<?> coll3) { return doIt(true, () -> StringPool.LEFT_BRACKET + columnsToString(true, column1, column2, column3) + StringPool.RIGHT_BRACKET, IN, inExpressionOfParam(coll1, coll2, coll3)); } /** * 获取 columnName */ protected String columnsToString(boolean onlyColumn, SFunction<T, ?>... columns) { return Arrays.stream(columns).map(i -> columnToString(i, onlyColumn)).collect(joining(StringPool.COMMA)); } protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) { Method method = ReflectUtil.getMethod(AbstractLambdaWrapper.class, "getColumn", SerializedLambda.class, Boolean.class); try { ReflectionUtils.makeAccessible(method); return (String) method.invoke(wrapperChildren, LambdaUtils.resolve(column), onlyColumn); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } private ISqlSegment inExpressionOfParam(List<?>... coll) { int collSize = coll.length; if (collSize == 0) { return () -> ""; } int size1 = coll[0].size(); if (size1 > 1000) { size1 = 1000; } List<String> resultList = new ArrayList<>(); List<String> formatList = new ArrayList<>(); for (int i = 0; i<collSize; i++) { formatList.add("{" + i + "}"); } String format = formatList.stream().collect(joining(StringPool.COMMA, StringPool.LEFT_BRACKET, StringPool.RIGHT_BRACKET)); for (int i=0; i < size1; i++) { Object[] objectArr = new Object[collSize]; for (int j = 0; j < collSize; j++) { if (coll[j].size() >= i) { objectArr[j] = coll[j].get(i); } } resultList.add(formatSql(format , objectArr)); } return () -> resultList.stream() .collect(joining(StringPool.COMMA, StringPool.LEFT_BRACKET, StringPool.RIGHT_BRACKET)); } protected final String formatSql(String sqlStr, Object... params) { Method method = ReflectUtil.getMethod(AbstractWrapper.class, "formatSql"); try { ReflectionUtils.makeAccessible(method); return (String) method.invoke(wrapperChildren, sqlStr, params); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } protected LambdaQueryWrapper<T> doIt(boolean condition, ISqlSegment... sqlSegments) { Method method = ReflectUtil.getMethod(AbstractWrapper.class, "doIt");; try { ReflectionUtils.makeAccessible(method); return (LambdaQueryWrapper<T>) method.invoke(wrapperChildren, true, sqlSegments); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }

拼接参数的主要逻辑在inExpressionOfParam里面
最后看一下使用

image.png

在具体需要用到的service里增加该方法,返回我们自己的实现类

image.png

直接使用即可

image.png

看一下sql执行日志,完美!

总结

今天接开发需求的一个例子,主要是像和大家分享平时遇上第三方包不支持的业务场景,如何快速的在其基础上扩展。
总结一下,首先明确目标,我们需要达到什么样的效果,其次,找到现有的实现类,模仿其中的写法。最后,利用反射机制,调用被保护的方法,达到我们的目的。

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

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

相关文章

【C++ 初阶路】--- 类和对象(末)

目录 一、const成员1.1 取地址及const取地址操作符重载 二、再谈构造函数2.1 构造函数体赋值2.2 初始化列表2.3 explicit关键字 三、static成员3.1 概念3.2 特性 四、友元4.1 友元函数4.2 友元类 五、内部类六、匿名对象 一、const成员 将const修饰的“成员函数”称之为const成…

软考《信息系统运行管理员》-1.4 常见的信息系统

1.4 常见的信息系统 常见的信息系统综述 财务系统 财务信息系统会计信息系统 办公自动化系统业务处理系统生产管理系统ERP系统客户关系管理系统人力资源系统 会计信息系统 主要任务是保证记账的正确性。 订单处理子系统库存子系统会计应收/应支系统总账子系统 财务信息系…

[CTF]-PWN:mips反汇编工具,ida插件retdec的安装

IDA是没有办法直接按F5来反汇编mips的汇编的&#xff0c;而较为复杂的函数直接看汇编不太现实&#xff0c;所以只能借用插件来反汇编 先配置环境&#xff0c;下载python3.4以上的版本&#xff0c;并将其加入到环境变量中 下载retdec 地址&#xff1a;Release v1.0-ida80 ava…

Rust Eq 和 PartialEq

Eq 和 PartialEq 在 Rust 中&#xff0c;想要重载操作符&#xff0c;你就需要实现对应的特征。 例如 <、<、> 和 > 需要实现 PartialOrd 特征: use std::fmt::Display;struct Pair<T> {x: T,y: T, }impl<T> Pair<T> {fn new(x: T, y: T) ->…

亲测可用!SM2269XT量产工具下载,SM2269XT开卡软件分享

国内固态硬盘常用&#xff0c;且有量产工具流传出来的主控厂商包括慧荣、群联、点序、英韧、得一微、瑞昱、联芸、迈威、国科、华澜微等等。 每个主控需要用各自对应的量产工具&#xff0c;不同的量产工具支持的闪存颗粒也有差异&#xff0c;因此要根据固态硬盘实际的主控型号…

小白快速入门canvas画海报

小编以微信小程序原生语言举例 wxml页面&#xff1a; <canvas type"2d" id"myCanvas" style"width:375px;height:667px;"></canvas> js页面&#xff1a; import drawQrcode from ../../../utils/qrcode/weapp.qrcode.esmdata: {…

vue3+ el-upload封装上传组件

组件功能介绍 上传格式限制上传大小限制上传文件数量限制自定义上传区上传成功回调禁用上传开关与点击上传自定义事件暴露所以上传文件列表&#xff08;uploadList&#xff09;与当前文件数据&#xff08;uploadLatestFile&#xff09; 组件代码Upload.vue <template>&l…

Vue-cli搭建一个项目

目录 vue-cli搭建项目 主要的功能 需要的环境 用 HbuilderX 搭建 vue-cli 项目 1、创建一个vue项目(2.6.10) 2、组件路由 首先&#xff1a;安装 其次&#xff1a; 1.在src文件夹下创建router目录,创建index.js 2.使用路由——在App.vue中添加路由视图 3.在main.js 中…

C语言学习记录(十一)——指针基本知识及运算

文章目录 前言1. 指针的概念2.指针变量的说明3. 指针的含义4. 指针运算①指针加减&#xff1a;②指针的关系运算符 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 1. 指针的概念 在C语言中&…

天正T20 专业建筑软件分享,天正T20全家桶软件安装包齐全!

天正T20 V9.0&#xff0c;在建筑工程领域中占据了举足轻重的地位。该软件以其高效、精确和易用的特点&#xff0c;赢得了广大工程师的青睐和信赖。 天正T20 V9.0软件具有强大的计算功能&#xff0c;可以精确地对建筑结构进行力学分析&#xff0c;包括静力分析、动力分析、稳定性…

使用Python实现钉钉Stream模式服务开发及内部程序通信

1、什么是Stream模式 Stream 模式是钉钉开放平台提供的一种集成方式&#xff0c;它可以监听机器人回调、事件订阅回调和注册卡片回调。使用 Stream 模式接入&#xff0c;钉钉开放平台将通过 Websocket 连接与应用程序通讯&#xff0c;Stream 模式将极大降低接入门槛和资源依赖…

Windows系统开启python虚拟环境

.\env4socre\Scripts\activate : 无法加载文件 E:\SocreMan\env4socre\Scripts\Activate.ps1&#xff0c;因为在此系统上禁止运行脚本。 环境&#xff1a;windows 11、vscode 1、用管理员权限打开powershell 输入set-executionpolicy remotesigned&#xff0c;选择Y 2、返回v…

信创认证 | Smartbi Insight V11成功适配申威3231处理器

在信息技术飞速发展的浪潮中&#xff0c;软硬件的深度融合与协同发展已成为推动行业创新的关键因素。 近日&#xff0c;思迈特商业智能与数据分析软件[简称&#xff1a;Smartbi Insight]V11在统信服务器操作系统V20和中电科申泰信息科技有限公司产品申威3231处理器环境下完成适…

【Linux 命令行参数解析函数getopt()】原理及直白理解

最近写代码恰好碰见getopt()这个函数&#xff0c;去网上找了很久&#xff0c;说实话&#xff0c;其他人写的有点看不懂&#xff0c;所以将我认为可以便于理解的地方描述一下&#xff1a; int getopt(int argc, char * const argv[], const char *optstring);首先理解这个函数的…

pdf合并,这三种方法学会了吗?

在信息爆炸的时代&#xff0c;PDF文档凭借其跨平台、不易修改的特性&#xff0c;成为了我们工作和学习中不可或缺的一部分。然而&#xff0c;当面对多个PDF文件需要合并成一个完整的文档时&#xff0c;许多人可能会感到头疼。今天&#xff0c;就让我们一起来探讨三种高效的PDF合…

OOXML入门学习

进入-飞入 <par> <!-- 这是一个并行动画序列的开始。"par"代表并行&#xff0c;意味着在这个标签内的所有动画将同时开始。 --><cTn id"5" presetID"2" presetClass"entr" presetSubtype"4" fill"hold&…

利用大模型技术,打造本地个人专属知识库

文章目录 利用大模型技术&#xff0c;打造本地个人专属知识库一 简介二 部署2.1 硬件要求2.2 部署信息2.3 通过docker部署、启动Ollama2.3 进入Ollama容器、拉取qwen2:7b模型2.4 测试Ollama2.5 通过docker部署、启动MaxKB2.6 登录MaxKB管理后台2.7 MaxKB系统配置2.8 创建知识库…

第56期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

一种自定义SPI通信协议

本文介绍一种自定义SPI通信协议。 项目开发过程中&#xff0c;有时候会涉及到主处理器或FPGA和MCU之间的SPI通信&#xff0c;涉及到通信就需要考虑通信协议&#xff0c;本文给出一种简单的通信协议。 1.协议格式 协议格式如下图。 其中&#xff0c;将40 bit划分为2大部分&am…

Spring Boot 过滤器和拦截器详解

目录 Spring Boot 过滤器1.什么是过滤器2.工作机制3.实现过滤器 Spring Boot 拦截器1. 什么是拦截器2. 工作原理3.实现4.拓展&#xff08;MethodInterceptor 拦截器&#xff09;实现 过滤器和拦截器区别过滤器和拦截器应用场景过滤器拦截器 Spring Boot 过滤器 1.什么是过滤器 …