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。这是我的个人号,非机构号,已从机构离职,重回一线了。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RT-Thread 内核对象管理框架

内核对象管理框架 RT-Thread采用内核对象管理系统来访问/管理所有内核对象&#xff0c;内核对象包含了内核中绝大部分设施&#xff0c;这些内核对象可以是静态分配的静态对象&#xff0c;也可以是从系统内存堆中分配的动态对象。 RT-Thread内核对象包括&#xff1a;线程&…

使用VisualStutio2022开发第一个C++程序

使用VisualStudio2022创建C项目 第一步&#xff1a;新建C的控制台应用 第二步&#xff1a;填写项目名称和代码存放位置&#xff0c;代码的存放目录不要有中文名 第三步:点击创建&#xff0c;VisualStudio会自动开始帮我们创建项目 第四步&#xff1a;项目创建好以后&…

2023,测试开发人的年终总结

根据TesterHome社区发帖整理。从该年终总结可以看出当前测试行业&#xff0c;哪怕是测试开发也是竞争很厉害。作为测试同仁&#xff0c;不但要掌握功能测试、接口测试、性能测试&#xff0c;掌握各种工具使用&#xff0c;还得懂开发&#xff0c;懂Java/Python&#xff0c;懂VUE…

洛谷——P3884 [JLOI2009] 二叉树问题(最近公共祖先,LCA)c++

文章目录 一、题目[JLOI2009] 二叉树问题题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示基本思路&#xff1a; 一、题目 [JLOI2009] 二叉树问题 题目描述 如下图所示的一棵二叉树的深度、宽度及结点间距离分别为&#xff1a; 深度&#xff1a; 4 4 4宽度&…

【AI提示词故事】《启示之星:寻找神殿的探险之旅》

故事叙述 在未来的某一天&#xff0c;人类发现了一个神秘的星球&#xff0c;被称为“启示之星”。传说在这颗星球上&#xff0c;有一座可以实现人类最大愿望的神殿。 启示之星 一支探险队被派往这颗星球&#xff0c;他们的目标是寻找神殿并实现自己的最大愿望。 在星球上&…

关于 curl 常用命令的使用整理【不定期更新】

目录 1. HTTP 请求2. 文件操作2.1 文件下载2.2 文件上传2.3 FTP 操作 3. 代理和网络设置4. 身份验证5. 调试和信息显示6. more and more curl 是一个用于在命令行下进行数据传输的工具&#xff0c;支持多种协议&#xff0c;包括 HTTP、HTTPS、FTP、FTPS、SCP、SFTP 等。它通常用…

结合ChatGPT和MINDSHOW自动生成PPT

总结/朱季谦 一、首先&#xff0c;通过chatGPT说明你的需求&#xff0c;学会提问是Ai时代最关键的一步。你需要提供一些关键信息&#xff0c;如果没有关键信息&#xff0c;就按照大纲方式让它设计&#xff0c;例如&#xff0c;我让它帮我写一份《2023年年中述职报告》的模版—…

项目管理进阶之序言

背景 可能任何一个程序猿/媛都有一个梦想&#xff0c;立志成为一个技术Leader&#xff0c;带领一个Team&#xff0c;完成一个组织中重要的Project。 有些人天赋异禀&#xff0c;光彩夺目&#xff0c;从小已形成的某些特质&#xff0c;足以让他/她胜任这个领域&#xff0c;我们…

第11章 GUI Page428 步骤七 设置圆,矩形,文字的前景色

运行效果&#xff1a; 关键代码&#xff1a; 分别设置圆&#xff0c;矩形&#xff0c;和文字的画笔颜色&#xff0c;其中文字的设置方法稍有不同 圆&#xff1a; 矩形&#xff1a; 文字&#xff1a;

CV算法面试题学习

本文记录了CV算法题的学习。 CV算法面试题学习 1 点在多边形内&#xff08;point in polygon&#xff09;2 高斯滤波器3 ViTPatch EmbeddingPosition EmbeddingTransformer Encoder完整的ViT模型 4 SE模块5 Dense Block6 Batch Normalization 1 点在多边形内&#xff08;point …

2000-2021年全国各省三农指标数据(700+指标)

2000-2021年全国各省三农指标数据合集&#xff08;700指标&#xff09; 1、时间&#xff1a;2000-2021年 2、来源&#xff1a;整理自2001-2022年农村年鉴 3、范围&#xff1a;31省市 4、指标&#xff1a;、农村经济在国民经济中的地位、社会消费品零售额_亿元、社会消费品零…

持续集成交付CICD:Jira 发布流水线

目录 一、实验 1.环境 2.GitLab 查看项目 3.Jira 远程触发 Jenkins 实现合并 GitLab 分支 4.K8S master节点操作 5.Jira 发布流水线 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构版本IP备注master1K8S master节点1.20.6192.168.204.180 jenkins…

Linux环境变量剖析

一、什么是环境变量 概念&#xff1a;环境变量&#xff08;environment variables&#xff09;一般是指在操作系统中用来指定操作系统运行环境的一些参数&#xff0c;是在操作系统中一个具有特定名字的对象&#xff0c;它包含了一个或多个应用程序所将使用到的信息&#xff0c…

Open3D 点云数据处理基础(Python版)

Open3D 点云数据处理基础&#xff08;Python版&#xff09; 文章目录 1 概述 2 安装 2.1 PyCharm 与 Python 安装 2.3 Anaconda 安装 2.4 Open3D 0.13.0 安装 2.5 新建一个 Python 项目 3 点云读写 4 点云可视化 2.1 可视化单个点云 2.2 同一窗口可视化多个点云 2.3…

蓝桥村的神秘农田

蓝桥村的神秘农田 问题描述 小蓝是蓝桥村的村长&#xff0c;他拥有一块神秘的农田。这块农田的奇特之处在于&#xff0c;每年可以种植两种作物&#xff0c;分别称为 "瑶瑶豆" 和 "坤坤果"。小蓝需要为每种作物选择一个整数的生长指数&#xff0c;瑶瑶豆的…

HEA---code

import matplotlib.pyplot as pltimport numpy as npfrom matplotlib.animation import FuncAnimationfrom matplotlib.offsetbox import OffsetImage, AnnotationBbox# 创建一个画布和坐标轴对象 fig, ax plt.subplots() # 创建一个参数t&#xff0c;范围是0到2π t np.lins…

关于“Python”的核心知识点整理大全39

目录 ​编辑 14.1.5 将 Play 按钮切换到非活动状态 game_functions.py 14.1.6 隐藏光标 game_functions.py game_functions.py 14.2 提高等级 14.2.1 修改速度设置 settings.py settings.py settings.py game_functions.py 14.2.2 重置速度 game_functions.py 1…

TCGA超过1G的病理wsi数据下载-gdc-client

使用网页端下载TCGA超过1G的病理wsi数据&#xff0c;数据下载到1G后就不能完整下载。遂采用gdc-client下载。 Win 环境下新建这个文件夹放在系统盘进行储存&#xff0c;否则会报错&#xff1a;ERROR: Unable to write state file: [WinError 17] 系统无法将文件移到不同的磁盘…

Node 源项目定制化、打包并使用全过程讲解

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是全栈工…

WGCLOUD快速部署方案 - 批量给Linux安装agent

有时候我们的Linux服务器比较多&#xff0c;一个一个安装比较花费时间&#xff0c;还要WGCLOUD提供了一个辅助工具wgcloud-bach-agent&#xff0c;可以批量给Linux服务器上传agent安装包&#xff0c;并自动解压和启动agent&#xff0c;可以大大减少我们的部署工作和时间 下载和…