3. Mybatis 中SQL 执行原理
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。
相关文章:
3. Mybatis 中SQL 执行原理
2. Mybatis 中SQL 执行原理 这里有两种方式,一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。 Spring 依赖注入 Mapper 的方式 Mapper 接口注入 SpringIOC 容器 Spring 容器在扫描 BeanDefinition 阶段会扫…...

第一次在RUST官方论坛上留言发布我的Rust板箱
第一次在RUST官方论坛上发帖子,有点紧张~地址在这里: 【My Rust Crate】obtains linux local information - The Rust Programming Language Forum (rust-lang.org)...

LabVIEW 智能化矿用定向钻机液压系统监测
简介 在矿用定向钻机的液压系统监测中,实现实时监控和异常预警对于保障设备运行的稳定性至关重要。传统的人工监测方法效率低下而且准确性不能满足要求,针对这种情况采用 LabVIEW 开发平台,设计并实现了一套智能化矿用定向钻机液压系统的状态…...
GO数据库操作
Golang 出色的 ORM 库为 GORM。 官网文档:https://gorm.io/docs/ 我们来说说增删改查的用法,更深入的研究可以去官网看看。 GORM功能概览: 关联(有一个、有多个、属于、多对多、多态性、单表继承)挂钩(创…...
PyTorch简单理解ChannelShuffle与数据并行技术解析
目录 torch.nn子模块详解 nn.ChannelShuffle 用法与用途 使用技巧 注意事项 参数 示例代码 nn.DataParallel 用法与用途 使用技巧 注意事项 参数 示例 nn.parallel.DistributedDataParallel 用法与用途 使用技巧 注意事项 参数 示例 总结 torch.nn子模块详…...

MySQL 8查询语句之查询所有字段、特定字段、去除重复字段、Where判断条件
《MySQL 8创建数据库、数据表、插入数据并且查询数据》里边有我使用到的数据。 再使用下方的语句补充一些数据: insert into Bookbought.bookuser(id,username,userphone,userage,sex,userpassword) values (11,Book Break,22245678911,18,male,good#111); insert…...

LLaMA-Factory添加adalora
感谢https://github.com/tsingcoo/LLaMA-Efficient-Tuning/commit/f3a532f56b4aa7d4200f24d93fade4b2c9042736和https://github.com/huggingface/peft/issues/432的帮助。 在LLaMA-Factory中添加adalora 1. 修改src/llmtuner/hparams/finetuning_args.py代码 在FinetuningArg…...

多端多用户万能DIY商城系统源码:自营+多商户入驻商城系统 独立部署 带完整的安装代码包以及搭建教程
电子商务行业日新月异,许多企业希望能够通过线上商城拓展业务。但是,传统商城系统往往无法满足多样化、个性化的需求,而且开发周期长、成本高。罗峰就来给大家分享一款多端多用户万能DIY商城系统源码,搭建简单。 以下是部分代码示…...
Qt 6之七:学习资源
Qt 6之七:学习资源 Qt是一种跨平台的C应用程序开发框架,它提供了一套丰富的工具和库,可以帮助开发者快速构建跨平台的应用程序,用于开发图形用户界面(GUI)和非GUI应用程序。 Qt 6之一:简介、安…...
解决大模型的幻觉问题:一种全新的视角
在人工智能领域,大模型已经成为了一个重要的研究方向。然而,随着模型规模的不断扩大,一种新的问题开始浮出水面,那就是“幻觉”问题。这种问题的出现,不仅影响了模型的性能,也对人工智能的发展带来了新的挑…...

mysql进阶-重构表
目录 1. 原因 2. 如何重构表呢? 2.1 命令1: 2.2 命令2: 2.3 命令3: 1. 原因 正常的业务开发,为什么需要重构表呢? 原因1:某张表存在大量的新增和删除操作,导致表经历过大量的…...

Element-ui图片懒加载
核心代码 <el-image src"https://img-blog.csdnimg.cn/direct/2236deb5c315474884599d90a85d761d.png" alt"我是图片" lazy><img slot"error" src"https://img-blog.csdnimg.cn/direct/81bf096a0dff4e5fa58e5f43fd44dcc6.png&quo…...

Linux系统——DNS解析详解
目录 一、DNS域名解析 1.DNS的作用 2.域名的组成 2.1域名层级结构关系特点 2.2域名空间构成 2.3域名的四种不同类型 2.3.1延伸 2.3.2总结 3.DNS域名解析过程 3.1递归查询 3.2迭代查询 3.3一次DNS解析的过程 4.DNS系统类型 4.1缓存域名服务器 4.2主域名服务器 4…...

初识Ubuntu
其实还是linux操作系统 命令都一样 但是在学习初级阶段,我还是将其分开有便于我的学习和稳固。 cat 查看文件 命令 Ubuntu工作中经常是用普通用户,在需要时才进行登录管理员用户 sudn -i 切换成管理用户 我们远程连接时 如果出现 hostname -I没有出现…...

Casper Network (CSPR)2024 年愿景:通过投资促进增长
Casper Network (CSPR)是行业领先的 Layer-1 区块链网络之一,通过推出了一系列值得关注的技术改进和倡议,已经为 2024 年做好了准备。 在过去的一年里,Casper Network (CSPR)不断取得里程碑式的进展,例如推…...

《MySQL系列-InnoDB引擎06》MySQL锁介绍
文章目录 第六章 锁1 什么是锁2 lock与latch3 InnoDB存储引擎中的锁3.1 锁的类型3.2 一致性非锁定读3.3 一致性锁定读3.4 自增长与锁3.5 外键和锁 4 锁的算法4.1 行锁的三种算法4.2 解决Phantom Problem 5 锁问题5.1 脏读5.2 不可重复读5.3 丢失更新 6 阻塞7 死锁 第六章 锁 开…...
获取多个PDF文件的内容并保存到excel上
# shuang # 开发时间:2023/12/9 22:03import pdfplumber import re import os import pandas as pd import datetimedef re_text(bt, text):# re 搜索正则匹配 包含re.compile包含的文字内容m1 re.search(bt, text)if m1 is not None:return re_block(m1[0])return…...

深入了解网络流量清洗--使用免费的雷池社区版进行防护
随着网络攻击日益复杂,企业面临的网络安全挑战也在不断增加。在这个背景下,网络流量清洗成为了确保企业网络安全的关键技术。本文将探讨雷池社区版如何通过网络流量清洗技术,帮助企业有效应对网络威胁。 ![] 网络流量清洗的重要性&#x…...
【FFMPEG应用篇】基于FFmpeg的转码应用(FLV MP4)
方法声明 extern "C" //ffmpeg使用c语言实现的,引入用c写的代码就要用extern { #include <libavcodec/avcodec.h> //注册 #include <libavdevice/avdevice.h> //设备 #include <libavformat/avformat.h> #include <libavutil/…...

LInux初学之路linux的磁盘分区/远程控制/以及关闭图形界面/查看个人身份
虚拟机磁盘分配 hostname -I 查看ip地址 ssh root虚拟就ip 远程连接 win10之后才有 远程控制重新启动 reboot xshell 使用(个人和家庭版 免费去官方下载) init 3 关闭界面 减小内存使用空间 init 5 回复图形界面 runlevel显示的是状态 此时和上…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
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 &…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...