2. Mybatis 中SQL 执行原理
这里有两种方式,一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。
Spring 依赖注入 Mapper 的方式
Mapper 接口注入 SpringIOC 容器
- Spring 容器在扫描 BeanDefinition 阶段会扫描 Mapper 接口类,并生成这些类的 MapperFactoryBean 的工厂 bean 定义。
 - Spring 容器在 createBean 阶段的时候,会根据 BeanDefintion 创建 bean。在创建完 factoryBean 的时候,会调用 factoryBean 的 getObject()方法,从 DefaultSqlSession 的 knownMapper 重获取 Mapper 接口类的 mapperProxy。
 - 使用 MapperProxy 创建出代理类。
 
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");} else {try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception var5) {throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);}}
}public T newInstance(SqlSession sqlSession) {MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);return this.newInstance(mapperProxy);
}protected T newInstance(MapperProxy<T> mapperProxy) {return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
 
Mapper 类调用
在 Service 层或者 Controller 层通过注解引入 Bean,这个时候引入的 Mapper 就是上文创建的 MapperProxy。MapperProxy 的增强逻辑首先过滤掉了 Object 类中的 toString()、equal()等方法。
如果调用的是 Object 类中的方法,直接放过不代理
对于 Mapper 接口中的方法进行代理。代理前先检查 methodCache 是否缓存了该方法的 invoke 逻辑。
default 方法的逻辑
非 default 方法的逻辑比较重要。
通过 PlainMethodInvoker 这个类代理了其他接口方法,代理逻辑在 MapperMethod 中。
MapperMethod 是最为核心的逻辑。MapperMethod 在执行构建方法时,就会创建一个 SqlCommand 和一个 MethodSignature 方法签名。
SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。
调用 execute 方法。传参为 MappedStatement 的增删改查的类型和参数
根据增删改查的类型选择不同的执行逻辑
增删改的逻辑:
- 解析参数得到 param,反射根据 mybatis 中参数注解解析
 - 
 sqlSession.insert(this.command.getName(), param)或者- 
 sqlSession.update(this.command.getName(), param)或者- 
 sqlSession.delete(this.command.getName(), param)或者- 处理结果返回值
 select 语句根据返回值类型不同调用不同执行逻辑
- returnVoid:返回值为空,且有专门的结果类型处理器
 - returnsMany:
 this.executeForMany(sqlSession, args);- returnsMap:
 this.executeForMap(sqlSession, args);- returnsCursor:
 this.executeForCursor(sqlSession, args);- returnsOne 返回一行:
 sqlSession.selectOne(this.command.getName(), param);flush 刷新类型的 SQL:
result = sqlSession.flushStatements();
如果调用的是 Object 类中的方法,直接放过布袋里
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 对于Object类中的方法,放过不增强,直接执行即可。return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) :  this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);}
}
 
在 MapperProxy 中有一个缓存结构 methodCache:Map<Method, MapperMethodInvoker> methodCache
增强逻辑中会先判断当前方法是否被缓存在 methodCache 中,如果没有,则创建一个放到缓存中。
 MapUtil.computeIfAbsent(this.methodCache, method, (m)->{/*创建缓存*/});
 
创建逻辑为:
MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {// default方法的逻辑DefaultMethodInvoker,java8和Java9的不一样。if (m.isDefault()) {return privateLookupInMethod == null ? new DefaultMethodInvoker(this.getMethodHandleJava8(method)) :new DefaultMethodInvoker(this.getMethodHandleJava9(method));} else {return new PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));}
});   
 
default 的先不用管 DefaultMethodInvoker,直接看 else 中的 PlainMethodInvoker:
创建一个 MapperMethod,然后 PlainMethodInvoker 在 invoke 方法中调用 MapperMethod 的方法 execute()。
在构造 MapperMethod 方法中,创建了一个 SqlCommand 。SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。在之后的 execute 方法中执行的就是 SqlCommand 中的 Mapped Statement。
// SqlCommand 封装了从SqlSession中Config配置中获取到的MappedStatement。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);
}private static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {this.mapperMethod = mapperMethod;}public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return this.mapperMethod.execute(sqlSession, args);}
}
 
接下来就是执行 SqlSession 中的增删改查方法了。可以先看一下 SqlCommand
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {String methodName = method.getName();Class<?> declaringClass = method.getDeclaringClass();MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);if (ms <span style="font-weight: bold;" class="mark"> null) {if (method.getAnnotation(Flush.class) </span> null) {throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);}this.name = null;this.type = SqlCommandType.FLUSH;} else {this.name = ms.getId();this.type = ms.getSqlCommandType();if (this.type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + this.name);}}
}
 
参数转化,然后 excute Sql,封装返回值:
public Object execute(SqlSession sqlSession, Object[] args) {Object result;Object param;switch (this.command.getType()) {case INSERT:// 将args参数数组转换成方法中的注解的参数param = this.method.convertArgsToSqlCommandParam(args);// 调用DefaultSqlSession的insert方法。// 处理结果返回值result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));break;case UPDATE:// 将args参数数组转换成方法中的注解的参数param = this.method.convertArgsToSqlCommandParam(args);// 调用DefaultSqlSession的update方法。// 处理结果返回值result = this.rowCountResult(sqlSession.update(this.command.getName(), param));break;case DELETE:// 将args参数数组转换成方法中的注解的参数param = this.method.convertArgsToSqlCommandParam(args);// 调用DefaultSqlSession的delete方法。// 处理结果返回值result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));break;case SELECT:// select 语句情况较多,根据返回值类型不同调用不同执行逻辑。// returnVoid:返回值为空,且有专门的结果类型处理器if (this.method.returnsVoid() && this.method.hasResultHandler()) {this.executeWithResultHandler(sqlSession, args);result = null;} else if (this.method.returnsMany()) {result = this.executeForMany(sqlSession, args);} else if (this.method.returnsMap()) {result = this.executeForMap(sqlSession, args);} else if (this.method.returnsCursor()) {result = this.executeForCursor(sqlSession, args);} else {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param);if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + this.command.getName());}if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");} else {return result;}}
 
SqlSessionTemplate 执行 Sql
在创建 SqlSession 的时候已经创建了 Executor。默认为 Simple
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");Assert.notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor());}
 
在 Spring Boot 自动配置这篇文章中已经讲过
- Configuration 类中有一个属性 
mappedStatements。这是一个 HashMap - 解析过后的 
MappedStatement被添加到了 map 中 
当我们的 SqlSession 在执行 sql 语句时,会先从 configuration 中拿到 sql。然后执行。
    private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {List var6;try {MappedStatement ms = this.configuration.getMappedStatement(statement);var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);} catch (Exception var10) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);} finally {ErrorContext.instance().reset();}return var6;}
 
然后看一下
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);}
 
再往下一层,就是执行 JDBC 那一套了,获取链接,执行,得到 ResultSet,解析 ResultSet 映射成 JavaBean。
相关文章:
2. Mybatis 中SQL 执行原理
这里有两种方式,一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。 Spring 依赖注入 Mapper 的方式 Mapper 接口注入 SpringIOC 容器 Spring 容器在扫描 BeanDefinition 阶段会扫描 Mapper 接口类,…...
平衡合规与发展天平, 激发数据要素价值
数字经济大潮汹涌,为了应对复杂的外部环境,培育企业内生竞争力,企业需要摆脱贪大求快的增长模式,转向依靠合规与发展的双轮驱动。 数字经济的核心在于数据。重视数据作为生产要素的战略意义,积极建设数据要素流通交易…...
JAVA毕业设计118—基于Java+Springboot的宠物寄养管理系统(源代码+数据库)
毕设所有选题: https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringboot的宠物寄养管理系统(源代码数据库)118 一、系统介绍 本系统分为管理员、用户两种角色 1、用户: 登陆、注册、密码修改、宠物寄养、寄养订单、宠物…...
oracle 19c容器数据库数据加载和传输-----SQL*Loader(一)
目录 数据加载 (一)控制文件加载 1.创建用户执行sqlldr 2.创建文本文件和控制文件 3.查看表数据 4.查看log文件 (二)快捷方式加载 1.system用户执行 2.查看表数据 3.查看log文件 外部表 数据加载和传输的工具࿱…...
超维空间M1无人机使用说明书——52、ROS无人机二维码识别与降落
引言:使用二维码引导无人机实现精准降落,首先需要实现对二维码的识别和定位,可以参考博客的二维码识别和定位内容。本小节主要是通过获取拿到的二维码位置,控制无人机全向的移动和降落,分为两种,一种是无人…...
Mac 安装Nginx教程
Nginx官网 Nginx官网英文 1.在终端输入brew search nginx 命令检查nginx是否安装了 2. 安装命令:brew install nginx 3. 查看Nginx信息命令brew info nginx 4. 启动 nginx方式:在终端里输入 nginx 5.查看 nginx 是否启动成功 在浏览器中访问http://l…...
【促销定价】背后的算法技术 1 - 业务问题拆解
【促销定价】背后的算法技术 1 - 业务问题拆解 01 业务背景02 关键挑战03 问题拆解04 核心结论参考文献 本文为转载,大佬的文章写的真好,给大佬推广推广,欢迎大家关注。 如侵删。 导读:在日常生活中,我们经常会遇见线上…...
CNAS中兴新支点——什么是安全测试,安全测试报告有什么作用,主要测试哪些内容?
1.安全测试在做什么? 扫描?在很多人眼中,做安全的就是整天那个工具在哪里扫描操作,使用各种不同的工具做扫描。 是的,扫描是安全测试很重要的一部分,扫描可快速有效发现问题。扫描工具的易用性࿰…...
【shell发送邮件】
一、centos系统 mail sendmail发送 安装mail [rootlocalhost ~]# yum install -y mailx安装sendmail [rootlocalhost ~]# yum install -y sendmail配置mail.rc文件 # 发送人,必须和发件人保持一致 set from769593qq.com # 邮箱服务器 set smtpsmtp.qq.com # 邮箱…...
Qt实现简单的分割窗口
最近在学习一些关于Qt的新知识,今天来讲述下我学习到的窗口分割,如果有不正确的,大家可以指正哦~ 首先,先看一下实现之后的简单效果吧!省的说的天花乱坠,大家却不知道说的是哪个部分。 功能实现 整体demo…...
简单易懂的PyTorch激活函数大全详解
目录 torch.nn子模块Non-linear Activations nn.ELU 主要特点与注意事项 使用方法与技巧 示例代码 图示 nn.Hardshrink Hardshrink函数定义 参数 形状 示例代码 图示 nn.Hardsigmoid Hardsigmoid函数定义 参数 形状 示例代码 图示 nn.Hardtanh HardTanh函数…...
x-cmd pkg | pdfcpu - 强大的 PDF 处理工具
目录 简介首次用户多功能支持性能表现安全的加密处理进一步阅读 简介 pdfcpu 是一个用 Go 编写的 PDF 处理库。同时它也提供 API 和 CLI。pdfcpu 提供了丰富的 PDF 操作功能,用户还能自己编写配置文件,用来管理和使用各种自定义字体并存储有效的默认配置…...
linux 压力测试 AB ApacheBench
ab的简介 ab是apachebench命令的缩写。 ab是apache自带的压力测试工具。ab非常实用,它不仅可以对apache服务器进行网站访问压力测试,也可以对或其它类型的服务器进行压力测试。比如nginx、tomcat、IIS等 ab的原理 ab的原理:ab命令会创建多…...
【云计算】云存储是什么意思?与本地存储有什么区别?
云计算环境下,衍生了云存储、云安全、云资源、云管理、云支出等等概念。今天我们就来了解下什么是云存储?云存储与本地存储有什么区别? 云存储是什么意思? 云存储是一种新型的数据管理方式,它通过网络将大量不同类型、…...
月入7K,19岁少年转行网优,他凭什么打破低学历魔咒?
专科未毕业、19岁,毫无专业技能,被匆匆赶进就业市场你会遇到什么? 毫无疑问,铺天盖地的拒绝和不合适,甚至有些公司连投递的资格都没有,这可能是所有低学历者求职过程中会遇到的“魔咒”。低学历似乎与低薪资…...
【C/C++】轻量级跨平台 开源串口库 CSerialPort
文章目录 1、简介2、支持的平台3、已经支持的功能4、Linux下使用5、使用vcpkg安装CSerialPort6、交叉编译7、效果图8、基于CSerialPort的应用8.1、CommMaster通信大师8.2、CommLite串口调试器 1、简介 Qt 的QSerialPort 已经是跨平台的解决方案,但Qt开发后端需要 Q…...
大创项目推荐 深度学习图像修复算法 - opencv python 机器视觉
文章目录 0 前言2 什么是图像内容填充修复3 原理分析3.1 第一步:将图像理解为一个概率分布的样本3.2 补全图像 3.3 快速生成假图像3.4 生成对抗网络(Generative Adversarial Net, GAN) 的架构3.5 使用G(z)生成伪图像 4 在Tensorflow上构建DCGANs最后 0 前言 &#…...
嵌入式系统复习--基于ARM的嵌入式程序设计
文章目录 上一篇编译环境ADS编译环境下的伪操作GNU编译环境下的伪操作ARM汇编语言的伪指令 汇编语言程序设计相关运算操作符汇编语言格式汇编语言程序重点C语言的一些技巧 下一篇 上一篇 嵌入式系统复习–Thumb指令集 编译环境 ADS/SDT IDE开发环境:它由ARM公司开…...
【C++入门到精通】异常 | 异常的使用 | 自定义异常体系 [ C++入门 ]
阅读导航 引言一、C异常的概念二、异常的使用1. 异常的抛出和捕获(1)throw(2)try-catch(3)catch(. . .)(4)异常的抛出和匹配原则(5)在函数调用链中异常栈展开…...
NX二次开发 Block UI 指定方位控件的应用
一、概述 NX二次开发中一般都是多个控件的组合,这里我首先对指定方位控件进行说明并结合选择对象控件,具体如下图所示。 二、实现功能获取方位其在选择面上原点的目标 2.1 在initialize_cb()函数中进行初始化,实现对象选择过滤面 //过滤平…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
