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

前言

最近碰上有个业务,查询的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.mzph.cn/web/36033.shtml

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

相关文章

重庆交通大学24计算机考研数据速览,专硕第二年招生,复试线321分!

重庆交通大学&#xff08;Chongqing Jiaotong University&#xff0c;CQJTU&#xff09;&#xff0c;是由重庆市人民政府和中华人民共和国交通运输部共建的一所交通特色、以工为主的多科性大学&#xff0c;入选“中西部高校基础能力建设工程”、“卓越工程师教育培养计划”、国…

企业级堡垒机JumpServer

文章目录 JumpServer是什么生产应用场景 Docker安装JumpServer1.Docker安装2.MySQL服务安装3.Redis服务安装4.key生成5.JumpServer安装6.登录验证 系统设置邮箱服务器用户和用户组创建系统审计员资产管理用户创建资产节点资产授权查看用户的资产监控仪表盘 命令过滤器创建命令过…

Model3C芯片方案--86彩屏中控面板Modbus协议说明

一、概述 Model3C芯片是一款基于RISC-V的高性能、国产自主、工业级高清显示与智能控制MCU&#xff0c;配备强大的2D图形加速处理器、PNG/JPEG解码引擎&#xff0c;并支持工业宽温。基于Model3C芯片的86彩屏中控面板&#xff0c;通过集成Modbus协议&#xff0c;实现了与多种控制…

前端存储都有哪些

cookie 、sessionStorage、localStorange、http缓存 、indexDB cookie 由服务器设置&#xff0c;在客户端存储&#xff0c;然后每次发起同源请求时&#xff0c;发送给服务器端。cookie最多能存储4K数据&#xff0c;它的生存时间由expires属性指定&#xff0c;并且cookie只能被…

涨点超强!图像特征提取最新方法!性能效率快到飞起

在图像处理领域&#xff0c;有一个非常关键的步骤&#xff1a;图像特征提取。它能给我们提供一种高效、准确且灵活的方式来描述和分析图像内容。 通过降低图像数据的维度&#xff0c;去除冗余和噪声信息&#xff0c;图像特征提取不但简化了后续处理过程&#xff0c;还能提高算…

ffmpeg使用mjpeg把yuvj420p编码为jpg图像

version #define LIBAVUTIL_VERSION_MAJOR 58 #define LIBAVUTIL_VERSION_MINOR 12 #define LIBAVUTIL_VERSION_MICRO 100 note 1. 通过*.jpg推测时&#xff0c;out_fmt为image2&#xff0c;打开*.jpg文件时&#xff0c;in_fmt为image2 但是out_fmt为image2时&#xff…

web项目打包成可以离线跑的exe软件

目录 引言打开PyCharm安装依赖创建 Web 应用运行应用程序打包成可执行文件结语注意事项 引言 在开发桌面应用程序时&#xff0c;我们经常需要将网页集成到应用程序中。Python 提供了多种方法来实现这一目标&#xff0c;其中 pywebview 是一个轻量级的库&#xff0c;它允许我们…

滑动窗口算法——部分OJ题详解

目录 关于滑动窗口 部分OJ题详解 209.长度最小的子数组 3.无重复字符的最长字串 1004.最大连续1的个数Ⅲ 1658.将x减到0的最小操作数 904.水果成篮 438.找到字符串中所有字母异位词 30.串联所有单词的子串 76.最小覆盖子串 关于滑动窗口 其实滑动窗口也是通过双指针…

Git(涵盖GitHub\Gitee码云\GitLab)

Git(涵盖GitHub\Gitee码云\GitLab) 文章目录 Git(涵盖GitHub\Gitee码云\GitLab)课程介绍Git概述官网介绍版本控制介绍两种版本控制工具集中式版本控制工具分布式版本控制工具 Git工作机制代码托管中心 Git安装和客户端的使用Git常用命令设置用户签名初始化本地库查看本地库状态…

C++——string类用法指南

一、前言 在C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要用户自己管理&#xff0c;稍…

C++编译时引入json/nlohmann文件报错

报错信息: In file included from /home/chenlang/catkin_ws/src/leanrning_communication/src/mysql/../utils/data.h:14:0,from /home/chenlang/catkin_ws/src/leanrning_communication/src/mysql/MyRobotDb.h:32,from /home/chenlang/catkin_ws/src/leanrning_communicatio…

学校选用SOLIDWORKS教育版进行授课的理由

在当代的工程与技术教育领域&#xff0c;计算机辅助设计软件&#xff08;CAD&#xff09;已经变成了一个不可缺少的教学辅助工具。SOLIDWORKS作为一个功能齐全且用户友好的CAD软件&#xff0c;其教育版本在学校教学环境中受到了广泛的欢迎。本文将对学校教学中选用SOLIDWORKS版…

eclipse基础工程配置( tomcat配置JRE环境)

文章目录 I eclipse1.1 工程配置1.2 编译工程1.3 添加 JRE for the project build pathII tomcat配置JRE环境2.1 Eclipse编辑tomcat运行环境(Mac版本)2.2 Eclipse编辑tomcat运行环境(windows版本)2.3 通过tomcat7W.exe配置运行环境(windows系统)I eclipse 1.1 工程配置 …

【motan rpc 懒加载】异常

文章目录 升级版本解决问题我使用的有问题的版本配置懒加载错误的版本配置了懒加载 但是不生效 lazyInit"true" 启动不是懒加载 会报错一次官方回复 升级版本解决问题 <version.motan>1.2.1</version.motan><dependency><groupId>com.weibo…

5G VONR

转载&#xff1a;VoNR呼叫流程介绍 (baidu.com) 使用5G RAN、5G Core和IMS的语音服务被称为新无线电VoNR上的语音&#xff0c;5G提供语音/视频通话等服务。 NR网络架构上的语音 NR语音网络体系结构由5G RAN、5G Core和IMS网络组成。下面显示了一个体系结构。&#xff08;仅包…

3.x86游戏实战-寄存器

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;2.x86游戏实战-跨进程读取血量 寄存器说明&#xff1a; 寄存器是处理器的一部&…

Windows部署MinIO,搭建本地对象存储服务

一、前言 二、MinIO介绍 三、Windows部署MinIO服务 1、准备工作 2、下载MinIO服务 3、启动MinIO服务 4、设置用户名密码 5、创建.bat文件启动服务 四、MinIO基本操作 1、存储桶管理 2、对象管理 3、数据查看 一、前言 基于外网的项目&#xff0c;可以使用阿里云等…

石墨舟氮气柜的特点和使用要求介绍

石墨舟是一种在半导体、太阳能光伏等高科技产业中广泛使用的专用工具&#xff0c;主要由高纯度石墨材料制成。它的形状通常像一只船&#xff0c;因此得名“石墨舟”。石墨舟主要用于承载硅片或其他基板材料通过各种高温处理过程&#xff0c;是制造半导体器件和太阳能电池片的关…

MIX OTP——监督动态子进程

现在&#xff0c;我们已经成功定义了我们的监督器&#xff0c;它将作为应用程序生命周期的一部分自动启动&#xff08;和停止&#xff09;。 但请记住&#xff0c;我们的 KV.Registry 在 handle_cast/2 回调中同时链接&#xff08;通过 start_link&#xff09;和监控&#xff…

分享一个导出数据到 Excel 的解决方案

前言 许多业务场景下需要处理和分析大量的数据&#xff0c;而 Excel 是广泛使用的文件格式&#xff0c;几乎所有人都能打开和查看 Excel 文件&#xff0c;因此将数据库中的原始数据处理后&#xff0c;导出到 Excel 是一个很常见的功能&#xff0c;对于数据管理、分析、备份、展…