当前位置: 首页 > news >正文

2. Mybatis 中SQL 执行原理

这里有两种方式,一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。

Spring 依赖注入 Mapper 的方式

Mapper 接口注入 SpringIOC 容器
  1. Spring 容器在扫描 BeanDefinition 阶段会扫描 Mapper 接口类,并生成这些类的 MapperFactoryBean 的工厂 bean 定义。
  2. Spring 容器在 createBean 阶段的时候,会根据 BeanDefintion 创建 bean。在创建完 factoryBean 的时候,会调用 factoryBean 的 getObject()方法,从 DefaultSqlSession 的 knownMapper 重获取 Mapper 接口类的 mapperProxy。
  3. 使用 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()等方法。

  1. 如果调用的是 Object 类中的方法,直接放过不代理

  2. 对于 Mapper 接口中的方法进行代理。代理前先检查 methodCache 是否缓存了该方法的 invoke 逻辑。

    1. default 方法的逻辑

    2. 非 default 方法的逻辑比较重要。

      1. 通过 PlainMethodInvoker 这个类代理了其他接口方法,代理逻辑在 MapperMethod 中。

      2. MapperMethod 是最为核心的逻辑。MapperMethod 在执行构建方法时,就会创建一个 SqlCommand 和一个 MethodSignature 方法签名。

        1. SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。

        2. 调用 execute 方法。传参为 MappedStatement 的增删改查的类型和参数

          1. 根据增删改查的类型选择不同的执行逻辑

          2. 增删改的逻辑:

            1. 解析参数得到 param,反射根据 mybatis 中参数注解解析
            2. sqlSession.insert(this.command.getName(), param) ​或者
            3. sqlSession.update(this.command.getName(), param) ​或者
            4. sqlSession.delete(this.command.getName(), param) ​或者
            5. 处理结果返回值
          3. select 语句根据返回值类型不同调用不同执行逻辑

            1. returnVoid:返回值为空,且有专门的结果类型处理器
            2. returnsMany:​this.executeForMany(sqlSession, args);
            3. returnsMap:​this.executeForMap(sqlSession, args);
            4. returnsCursor:​this.executeForCursor(sqlSession, args);
            5. returnsOne 返回一行:sqlSession.selectOne(this.command.getName(), param);
          4. 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 自动配置这篇文章中已经讲过

  1. Configuration 类中有一个属性 mappedStatements​。这是一个 HashMap
  2. 解析过后的 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 执行原理

这里有两种方式&#xff0c;一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。 Spring 依赖注入 Mapper 的方式 Mapper 接口注入 SpringIOC 容器 Spring 容器在扫描 BeanDefinition 阶段会扫描 Mapper 接口类&#xff0c…...

平衡合规与发展天平, 激发数据要素价值

数字经济大潮汹涌&#xff0c;为了应对复杂的外部环境&#xff0c;培育企业内生竞争力&#xff0c;企业需要摆脱贪大求快的增长模式&#xff0c;转向依靠合规与发展的双轮驱动。 数字经济的核心在于数据。重视数据作为生产要素的战略意义&#xff0c;积极建设数据要素流通交易…...

JAVA毕业设计118—基于Java+Springboot的宠物寄养管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringboot的宠物寄养管理系统(源代码数据库)118 一、系统介绍 本系统分为管理员、用户两种角色 1、用户&#xff1a; 登陆、注册、密码修改、宠物寄养、寄养订单、宠物…...

oracle 19c容器数据库数据加载和传输-----SQL*Loader(一)

目录 数据加载 &#xff08;一&#xff09;控制文件加载 1.创建用户执行sqlldr 2.创建文本文件和控制文件 3.查看表数据 4.查看log文件 &#xff08;二&#xff09;快捷方式加载 1.system用户执行 2.查看表数据 3.查看log文件 外部表 数据加载和传输的工具&#xff1…...

超维空间M1无人机使用说明书——52、ROS无人机二维码识别与降落

引言&#xff1a;使用二维码引导无人机实现精准降落&#xff0c;首先需要实现对二维码的识别和定位&#xff0c;可以参考博客的二维码识别和定位内容。本小节主要是通过获取拿到的二维码位置&#xff0c;控制无人机全向的移动和降落&#xff0c;分为两种&#xff0c;一种是无人…...

Mac 安装Nginx教程

Nginx官网 Nginx官网英文 1.在终端输入brew search nginx 命令检查nginx是否安装了 2. 安装命令&#xff1a;brew install nginx 3. 查看Nginx信息命令brew info nginx 4. 启动 nginx方式&#xff1a;在终端里输入 nginx 5.查看 nginx 是否启动成功 在浏览器中访问http://l…...

【促销定价】背后的算法技术 1 - 业务问题拆解

【促销定价】背后的算法技术 1 - 业务问题拆解 01 业务背景02 关键挑战03 问题拆解04 核心结论参考文献 本文为转载&#xff0c;大佬的文章写的真好&#xff0c;给大佬推广推广&#xff0c;欢迎大家关注。 如侵删。 导读&#xff1a;在日常生活中&#xff0c;我们经常会遇见线上…...

CNAS中兴新支点——什么是安全测试,安全测试报告有什么作用,主要测试哪些内容?

1.安全测试在做什么&#xff1f; 扫描&#xff1f;在很多人眼中&#xff0c;做安全的就是整天那个工具在哪里扫描操作&#xff0c;使用各种不同的工具做扫描。 是的&#xff0c;扫描是安全测试很重要的一部分&#xff0c;扫描可快速有效发现问题。扫描工具的易用性&#xff0…...

【shell发送邮件】

一、centos系统 mail sendmail发送 安装mail [rootlocalhost ~]# yum install -y mailx安装sendmail [rootlocalhost ~]# yum install -y sendmail配置mail.rc文件 # 发送人&#xff0c;必须和发件人保持一致 set from769593qq.com # 邮箱服务器 set smtpsmtp.qq.com # 邮箱…...

Qt实现简单的分割窗口

最近在学习一些关于Qt的新知识&#xff0c;今天来讲述下我学习到的窗口分割&#xff0c;如果有不正确的&#xff0c;大家可以指正哦~ 首先&#xff0c;先看一下实现之后的简单效果吧&#xff01;省的说的天花乱坠&#xff0c;大家却不知道说的是哪个部分。 功能实现 整体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 操作功能&#xff0c;用户还能自己编写配置文件&#xff0c;用来管理和使用各种自定义字体并存储有效的默认配置…...

linux 压力测试 AB ApacheBench

ab的简介 ab是apachebench命令的缩写。 ab是apache自带的压力测试工具。ab非常实用&#xff0c;它不仅可以对apache服务器进行网站访问压力测试&#xff0c;也可以对或其它类型的服务器进行压力测试。比如nginx、tomcat、IIS等 ab的原理 ab的原理&#xff1a;ab命令会创建多…...

【云计算】云存储是什么意思?与本地存储有什么区别?

云计算环境下&#xff0c;衍生了云存储、云安全、云资源、云管理、云支出等等概念。今天我们就来了解下什么是云存储&#xff1f;云存储与本地存储有什么区别&#xff1f; 云存储是什么意思&#xff1f; 云存储是一种新型的数据管理方式&#xff0c;它通过网络将大量不同类型、…...

月入7K,19岁少年转行网优,他凭什么打破低学历魔咒?

专科未毕业、19岁&#xff0c;毫无专业技能&#xff0c;被匆匆赶进就业市场你会遇到什么&#xff1f; 毫无疑问&#xff0c;铺天盖地的拒绝和不合适&#xff0c;甚至有些公司连投递的资格都没有&#xff0c;这可能是所有低学历者求职过程中会遇到的“魔咒”。低学历似乎与低薪资…...

【C/C++】轻量级跨平台 开源串口库 CSerialPort

文章目录 1、简介2、支持的平台3、已经支持的功能4、Linux下使用5、使用vcpkg安装CSerialPort6、交叉编译7、效果图8、基于CSerialPort的应用8.1、CommMaster通信大师8.2、CommLite串口调试器 1、简介 Qt 的QSerialPort 已经是跨平台的解决方案&#xff0c;但Qt开发后端需要 Q…...

大创项目推荐 深度学习图像修复算法 - opencv python 机器视觉

文章目录 0 前言2 什么是图像内容填充修复3 原理分析3.1 第一步&#xff1a;将图像理解为一个概率分布的样本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开发环境&#xff1a;它由ARM公司开…...

【C++入门到精通】异常 | 异常的使用 | 自定义异常体系 [ C++入门 ]

阅读导航 引言一、C异常的概念二、异常的使用1. 异常的抛出和捕获&#xff08;1&#xff09;throw&#xff08;2&#xff09;try-catch&#xff08;3&#xff09;catch(. . .)&#xff08;4&#xff09;异常的抛出和匹配原则&#xff08;5&#xff09;在函数调用链中异常栈展开…...

NX二次开发 Block UI 指定方位控件的应用

一、概述 NX二次开发中一般都是多个控件的组合&#xff0c;这里我首先对指定方位控件进行说明并结合选择对象控件&#xff0c;具体如下图所示。 二、实现功能获取方位其在选择面上原点的目标 2.1 在initialize_cb()函数中进行初始化&#xff0c;实现对象选择过滤面 //过滤平…...

时序数据库 + 微服务:MyEMS 如何支撑千万级测点的能源管理平台

在工业能源数字化的实践中&#xff0c;一个常被低估的命题是&#xff1a;当一家大型制造集团拥有数十个厂区、每个厂区部署数千台智能表计和传感器&#xff0c;全集团同时在线的测点数量突破千万级别时&#xff0c;能源管理系统应当具备怎样的技术底色&#xff1f;这不是一个关…...

13.解决 99% 刷机故障!小米 / 华为 / OV / 苹果通用救砖与分区修复教程

摘要 本文面向具备基础电子知识的技术人员,系统阐述主流品牌手机(华为、小米、OPPO、vivo、一加、苹果)的刷机与维修全流程。内容涵盖底层引导加载机制、分区表结构、签名验证原理,并提供完整的刷机脚本与维修诊断工具链。所有代码均已测试,可直接在Linux/Windows环境下运…...

XLASSO:高维稀疏建模在极端事件尾部预测中的原理与实践

1. 项目概述&#xff1a;当极端事件遇见高维稀疏性在金融风险管理、气候极端事件预测或是网络流量异常检测中&#xff0c;我们常常面临一个共同的挑战&#xff1a;如何基于有限的历史极端观测数据&#xff0c;对未来可能发生的、更为罕见的“黑天鹅”事件做出可靠预测&#xff…...

PDF补丁丁:5个高效PDF处理方案解决办公文档管理痛点

PDF补丁丁&#xff1a;5个高效PDF处理方案解决办公文档管理痛点 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱&#xff0c;可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档&#xff0c;探查文档结构&#xff0c;提取图片、转成图片等等 项目地址: https://gitc…...

华为openEuler系统下,永久配置JAVA_HOME环境变量的三种方法(含/etc/profile与~/.bashrc对比)

华为openEuler系统下永久配置JAVA_HOME的深度实践指南在openEuler系统中部署Java应用时&#xff0c;环境变量配置的持久性直接影响开发效率和系统稳定性。许多开发者遇到过这样的困扰&#xff1a;明明在终端中配置了JAVA_HOME&#xff0c;重启服务器后所有设置"消失"…...

《AI智能体(Agent)深度解析:2026年从被动对话到主动自主工作的技术革命》

近两年大模型完成了从“参数堆叠”到“能力进化”的跨越&#xff0c;而2026年AI行业的核心变革趋势&#xff0c;早已不再是更大参数的模型比拼&#xff0c;而是AI智能体&#xff08;Agent&#xff09;的规模化落地。传统AI对话模式&#xff0c;本质是被动响应式交互&#xff0c…...

Gemini LTV建模实战手册:从POC验证、规模化推理、监管审计到知识沉淀——覆盖7大关键节点的稀缺性价值锚定法

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;Gemini生命周期价值分析 Gemini模型的生命周期价值&#xff08;Lifetime Value, LTV&#xff09;并非仅由初始部署成本或单次推理费用决定&#xff0c;而是贯穿于模型选型、集成、运行、监控、迭代与退役的全…...

机器学习原子间势与连续介质模型在柔性InSe扭转双层原子重构研究中的应用

1. 项目概述&#xff1a;当柔性二维材料遇上扭转角在二维材料的世界里&#xff0c;一个简单的“扭转”操作&#xff0c;往往能打开一扇通往新奇物理现象的大门。从魔角石墨烯中发现的超导和关联绝缘态&#xff0c;到过渡金属硫族化合物&#xff08;TMDs&#xff09;中的莫尔激子…...

【Java EE】IPv6

IPv6引言IPv6 地址表示IPv6 地址类型地址范围详解多播地址结构IPv6 与 IPv4 的主要区别IPv6 首部格式扩展首部IPv6 地址配置方式无状态地址自动配置&#xff08;SLAAC&#xff09;有状态配置&#xff08;DHCPv6&#xff09;手动配置邻居发现协议&#xff08;NDP&#xff09;IPv…...

机器学习势函数在暗物质探测中的应用:计算晶体缺陷存储能

1. 项目概述&#xff1a;当机器学习势函数遇上暗物质探测在粒子物理与凝聚态物理的交叉前沿&#xff0c;有一个看似微小却至关重要的物理细节&#xff0c;正困扰着新一代的暗物质与中微子探测实验&#xff1a;当一个来自宇宙的弱相互作用粒子&#xff08;WIMP&#xff09;或一个…...