MyBatis-Plus中默认方法对应的SQL到底长啥样?

我希望成为:自媒体圈中技术最好、实战经验最丰富的达人,技术圈中最会分享的架构师。加油!
我的公众号:Hoeller

过段时间要给公司同事做Mybatis-Plus相关的培训,所以抓紧时间看看Mybatis-Plus的源码,顺便也分享出来让大家看看内容如何,希望大家多给意见。

在用Mybatis-Plus时,我们通常会继承BaseMapper接口,比如:

@Mapper
public interface DaduduMapper extends BaseMapper<MyEntity> {@Select("select 1")String test();
}

BaseMapper接口中提供了很多增删查改的方法,比如:

public interface BaseMapper<T> extends Mapper<T> {int insert(T entity);int deleteById(Serializable id);T selectById(Serializable id);// 我删除了很多
}

我一直有个疑问,为什么我们在执行这些方法时就能执行对应的SQL逻辑,比如执行selectById方法就会真正根据id去查数据并返回,这些方法上并没有使用@Select注解定义SQL,也找不到相应的XML文件,**这些方法对应的SQL到底长什么样?是如何生成的?**这篇文章就来给大家粗浅的分析一下。

上面我们自定义了一个DadududuMapper接口,并自定义了一个test()方法和对应SQL语句:

@Mapper
public interface DaduduMapper extends BaseMapper<MyEntity> {@Select("select 1")String test();
}

在Mybatis中会解析test()方法以及@Select中的SQL,生成一个MappedStatement对象,该对象长下面这样:
image.png

可以看到,一个MappedStatement对象包含了两个非常重要的部分:

  1. 方法部分:比如id属性、parameterMap属性,表示方法相关的信息
  2. SQL部分:比如sqlSource属性,表示方法对应的SQL语句信息

对于我们自定义的方法,生成这两部分信息是比较自然的,解析方法和SQL就可以了。那对于BaseMapper接口中的方法呢?它的SQL部分信息是如何得来的呢?比如BaseMapper接口中存在一个selectById()方法,这个方法对应的SqlSource对象是如何生成的呢?

在Mybatis-Plus的源码中有这样一段代码:

// 判断type是不是继承了Mapper接口
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {parserInjector();
}

其中type参数就是我们定义的DadududuMapper接口,而isSupperMapperChildren()方法会判断DadududuMapper接口是不是继承了Mapper接口,如果是,则执行parserInjector()方法。
image.png
由于BaseMapper继承了Mapper接口,所以DadududuMapper接口自然就继承了Mapper接口,所以在解析DadududuMapper接口时会执行parserInjector()方法:

void parserInjector() {// 先得到DefaultSqlInjector对象,再执行inspectInject方法GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}

parserInjector()方法默认会获取到一个DefaultSqlInjector对象,看名字就知道跟SQL有关了,而且叫做SQL注入器,是不是有点感觉了,它是不是用来生成SQL并注入或绑定到BaseMapper接口中各个方法的?我们继续看inspectInject()方法。

该方法中有几段关键的代码,我分段来分析,第一段:

Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);

传入的mapperClass参数就是DaduduMaper接口,返回的是MyEntity,也就是DaduduMapper接口指定的泛型:
image.png

然后:

// 根据modelClass生成表信息
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);

根据modelClass,也就是MyEntity类,生成TableInfo对象,也就是表相关的信息。

然后:

// 得到AbstractMethod集合
List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);// 遍历集合进行注入
if (CollectionUtils.isNotEmpty(methodList)) {methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
}

这里的this,既DefaultSqlInjector对象,它的getMethodList()方法实现如下:

public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder().add(new Insert()).add(new Delete()).add(new Update()).add(new SelectCount()).add(new SelectMaps()).add(new SelectObjs()).add(new SelectList());// 如果有主键,则添加跟主键相关的对象if (tableInfo.havePK()) {builder.add(new DeleteById()).add(new DeleteBatchByIds()).add(new UpdateById()).add(new SelectById()).add(new SelectBatchByIds());} else {// 日志打印而已...}return builder.build().collect(toList());
}

以上代码并不难,就是生成一个List,并且该List中存储一些Insert、Delete、Update、SelectById等对象,这些对象的父类是AbstractMethod类,得到List后就遍历这些对象,分别调用这些对象的inject(),该方法中会调用injectMappedStatement()抽象方法,我们拿SelectById对象举例,最终就会调用它所实现的injectMappedStatement()方法,它的逻辑是:

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {// 是一个枚举SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;// 生成SQL语句后封装为SqlSource对象SqlSource sqlSource = super.createSqlSource(configuration, String.format(sqlMethod.getSql(),sqlSelectColumns(tableInfo, false),tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, true)), Object.class);// 根据方法信息和SqlSource对象生成并返回MappedStatement对象return this.addSelectMappedStatementForTable(mapperClass, methodName, sqlSource, tableInfo);
}

这个方法里面就会负责生成SqlSource对象,有了SqlSource对象在结合方法信息,就可以生成MappedStatement对象了,注意该返回的就是MappedStatement对象。

在看具体生成SqlSource对象的逻辑之前,到此我想先总结一下,我们上面看到了有很多Insert、Delete、Update、SelectById等对象,而这些对象其实就分别对应了BaseMapper接口中的方法,比如selectById()方法对应的就是SelectById对象,并且每个对象中都有injectMappedStatement()方法,也就是每个对象有各自的策略来生成Sql,所以,我们要知道selectById()方法对应的SQL长什么样,那我们就找到SelectById对象看它里面是如何生成SqlSource对象就可以了,SqlSource对象中就包含了具体的SQL语句。

并且我们的入口是解析DaduduMapper接口,在这个过程中Mybatis-Plus会把Insert、Delete、Update、SelectById这些对象获取出来,然后遍历它们一个一个按照各自的策略进行解析得到SqlSource对象,并得到对应的MappedStatement对象,所以在解析DaduduMapper接口时,除开我们自定义的test()方法会生成一个MappedStatement对象,其实额外还会得到BaseMapper中各个方法所对应的MappedStatement对象,而最终在调用方法时,就会根据方法得到MappedStatement对象,从而得到SqlSource对象,从而执行SQL语句。

总结完了,不知道大家是否明白了呢?如果有疑问欢迎联系我讨论,下面有我的联系方式。

我们继续来看SqlSource对象的生成过程,本文我只以SelectById对象为例,来看看它是如何生成SQL的,其他对象后续文章或大家可以自行分析。

其实上面SqlSource部分的代码中,最关键的就是String.format()部分:

// 枚举
SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;String.format(sqlMethod.getSql(),sqlSelectColumns(tableInfo, false),tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, true))

它的作用是拼装SQL语句,所以很重要,其中它用到的SqlMethod对象是一个枚举,该枚举就更重要了,部分内容如下:
image.png
image.png
所以,这个SqlMethod枚举定义了BaseMapper中各个方法对应的SQL模板,比如SelectById对象中取的就是该枚举中的SELECT_BY_ID

SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s} %s"),

它对应的SQL模板为:SELECT %s FROM %s WHERE %s=#{%s} %s,所以,回到上面的代码:

String.format(sqlMethod.getSql(),sqlSelectColumns(tableInfo, false),tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, true))
  1. sqlMethod.getSql()得到就是枚举中的SQL模板,比如SELECT %s FROM %s WHERE %s=#{%s} %s
  2. sqlSelectColumns(tableInfo, false)方法会根据表信息返回要查询的字段信息,比如"id,name"
  3. tableInfo.getTableName()得到表名,比如"my_entity"
  4. tableInfo.getKeyColumn()得到主键名,比如"id"
  5. tableInfo.getKeyProperty()得到主键参数名,比如"id"
  6. tableInfo.getLogicDeleteSql(true, true)在SelectById对象中得到的是空字符串""

最终把这些信息format到SQL模板中就得到了最终SQL:SELECT id,name FROM my_entity WHERE id=#{id}),而这,就是BaseMapper接口中selectById()方法所对应的SQL,此次应该有掌声或点赞、分享、收藏。

再次提醒,如果你想知道BaseMapper接口中deleteBatchIds()方法对应的SQL语句是怎样的,那你直接看DeleteBatchByIds对象以及SqlMethod枚举中的DELETE_BATCH_BY_IDS就可以啦,比如:

DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量删除数据", "<script>\nDELETE FROM %s WHERE %s IN (%s)\n</script>"),

好啦,本文就分析到这,其实为了阅读体验,这中间我省去了很多细节,后续再来分享吧,期待我后续的文章吗?关注我的公众号:Hoeller

我是大都督周瑜,欢迎关注我的公众号:Hoeller。这是我的个人号,非机构号,已从机构离职,重回一线了。

现在网络上有很多的文章和视频,但是其中大部分都是一些八股文和理论知识,由不同的人翻来覆去,重复写、重复发,这种做法对作者本身是有用的,但是对于大部分读者来说可能是没有意义的,对于读者来说,不管是面试还是实际工作,我相信实战经验才是真正有价值的,所以我现在的分享都来源于我的实际工作,再结合我多年讲课的经验,希望做到把实战经验、理论知识、授课技巧融合起来,让读者能轻轻松松的从我的文章或视频中学到真正有价值的知识。

我希望成为:自媒体圈中技术最好、实战经验最丰富的达人,技术圈中最会分享的架构师。加油!

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

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

相关文章

Kubernetes 架构原则和对象设计

什么是 Kubernetes Kubernetes 是谷歌开源的容器集群管理系统 • 基于容器的应用部署、维护和滚动升级&#xff1b; • 负载均衡和服务发现&#xff1b; • 跨机器和跨地区的集群调度&#xff1b; • 自动伸缩&#xff1b; • 无状态服务和有状态服务&#xff1b; • 插件机制…

线程的同步与互斥

抢票的例子 竞争过程 进程A被切走 进程B被切走 结论&#xff1a; 互斥 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); mutex: 指向要初始化的互斥锁的指针。attr: 用于设置互斥锁属性的指针&#xff0c;通常可以传入 NULL 以使用默认属性…

Linux命令指南

Linux上显示某个进程的线程方式 方法一&#xff1a;PS 在ps命令中&#xff0c;“-T”选项可以开启线程查看。下面的命令列出了由进程号为的进程创建的所有线程。 ps -T -p <pid>“SPID”栏表示线程ID&#xff0c;而“CMD”栏则显示了线程名称。 方法二&#xff1a; T…

深入了解 Git 分支合并冲突解决步骤

目录 前言1 检测合并冲突2 手动解决冲突2.1 打开冲突文件2.2 手动解决冲突 3 标记解决后的文件4 完成合并5 提交合并后的内容6 验证合并结语 前言 在协作开发中&#xff0c;当不同分支对同一文件的相同位置进行修改时&#xff0c;往往会出现合并冲突。这些冲突需要开发者手动介…

VSCODE 配置远程调试环境

以下内容为本人的著作&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「englyf」https://mp.weixin.qq.com/s/f1KZOlL92ojes-r2l9rlCw 我的需求是&#xff0c;在Windows桌面环境下&#xff0c;通过 VSCODE 远程调试在服务器(或者其它远程主机)的工程代码。其实就…

【愚公系列】2023年12月 HarmonyOS教学课程 041-Stage模型(概述和组件配置)

&#x1f3c6; 作者简介&#xff0c;愚公搬代码 &#x1f3c6;《头衔》&#xff1a;华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xf…

计算机网络——数据链路层(三)

前言: 前面我们已经对计算机网络的物理层有了一个大概的了解&#xff0c;今天我们学习的是物理层服务的上一层数据链路层&#xff0c;位于物理层和网络层之间。数据链路层在物理层提供的服务的基础上向网络层提供服务&#xff0c;其最基本的服务是将源自物理层来的数据可靠地传…

springCould中的consul-从小白开始【4】

目录 1.consul介绍 ❤️❤️❤️ 2.安装 ❤️❤️❤️ 3.创建8006模块 ❤️❤️❤️ 4.创建80模块❤️❤️❤️ 1.consul介绍 ❤️❤️❤️ Consul 是一种用于服务发现、配置和分布式一致性的开源软件。它由HashiCorp开发和维护&#xff0c;可用于帮助构建和管理现代化的分布…

【四】记一次关于架构设计从0到1的讨论

记一次关于架构设计从0到1的讨论 简介&#xff1a; 在一次面试中和面试官讨论起来架构设计这个话题&#xff0c;一聊就不知不觉一个小时了&#xff0c;感觉意犹未尽。现在回想起来感觉挺有意思的&#xff0c;古人说独学而无友则孤陋而寡闻&#xff0c;的确是这样的&#xff0c…

使用ArcMap对工厂选址

文章目录 题目流程1&#xff0c;添加河流数据和高程数据2&#xff0c;对河流数据进行选取3&#xff0c;对高程数据进行选取并划定工厂选址范围1&#xff0c;栅格提取法2&#xff0c;重分类法 4&#xff0c;汇总 结果 题目 实验名称&#xff1a;工厂选址 实验目的及要求&#x…

动态规划系列 | 最长上升子序列模型(上)

文章目录 最长上升子序列回顾题目描述问题分析程序代码复杂度分析 怪盗基德的滑翔翼题目描述输入格式输出格式 问题分析程序代码复杂度分析 登山题目描述输入格式输出格式 问题分析程序代码复杂度分析 合唱队形题目描述输入格式输出格式 问题分析程序代码复杂度分析 友好城市题…

WorkPlus超级APP助力企业节省IT人力成本,实现快速移动化

在信息化时代&#xff0c;移动应用已经成为企业发展的重要组成部分。然而&#xff0c;开发和维护原生客户端的成本却相对较高&#xff0c;需要大量的iOS、安卓和桌面端工程师。为了解决这一问题&#xff0c;WorkPlus作为一个功能完备的超级APP&#xff0c;为企业节约了大量的IT…