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()函数中进行初始化,实现对象选择过滤面 //过滤平…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
