【Mybatis源码分析】Mybatis 是如何实现预编译的?

Mybatis 是如何实现预编译的?

  • 一、前言
  • 二、源码分析
  • 三、总结

一、前言

在介绍 Mybatis 是如何实现预编译之前,需提前知道俩个预备知识:

  1. MySQL的运行流程(对应的 SQL 会成为一个文本-》查询缓存(8.0后没了)-》解析器(解析SQL,对SQL进行预处理,也就是判断语法等操作)-》查询优化(比如底层的索引优化,如所用联合索引的顺序调换优化查询等等)-》执行SQL,从存储引擎中得到数据后返回)。
  2. Mybatis 是对 JDBC 的封装(emmmm,这个大家都知道🤣)

在这里插入图片描述

SQL 预编译概述:数据库收到 SQL 语句之后,需要词法和语义解析,以优化 SQL 语句,制定执行计划。这需要花费一些时间,但是很多情况下,我们的同一条 sql 语句可能会反复的执行,或者每次执行的时候只有个别的值不同(比如:select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过词法语义解析、语句优化、制定执行计划等等,则效率明显不行。为了解决这个问题,于是有了预编译,预编译语句就是将这类语句中的值用占位符替代,可以视为将 sql 语句模块化或者说参数化。一次编译、多次运行,省去了解析优化等过程。预编译语句被 DB 的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要重复编译了,只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。 并不是所有预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。在 JDBC 中,预编译是通过 PreparedStatement 和 占位符 来实现的。PreparedStatement 也是 Mybatis 默认的语句执行器。

预编译的作用:

  1. 减少编译次数,提升性能:预编译之后的 sql 多数情况下可以直接执行,DBMS(数据库管理系统)不需要再次编译。越复杂的 sql,往往编译的复杂度就越大;
  2. 防止 SQL 注入:使用预编译,后面注入的参数将不会再次触发 SQL 编译。也就是说,对于后面注入的参数,系统将不会认为它会是一个 SQL 命令,而默认是一个参数,参数中的 or 或 and 等(SQL 注入常用伎俩,说来说去就这些)就不是 SQL 语法保留字了。

二、源码分析

在直观的分析源码之前,还得提一下我们从 SQLSource.getBoundSql 中拿到的 BoundSQL 对象中的 sql 属性,这得回到前面小编所写的动态解析SQL的博客里 【Mybatis源码分析】动态标签的底层原理,DynamicSqlSource源码分析 ,里面阐述了不管是RawSqlSource还是DynamicSqlSource,都会通过 SqlSourceBuilder 去解析 sql 文本的 #{} ,将其替换成 ? 占位符。

首先我们还得知道 Mybatis 是通过执行器去执行对应的 SQL 的(关于执行器我再写篇博客详细解释),Mybatis 为我们提供了三种执行器:SimpleExecutor(简单执行器、也是默认执行器)、ReuseExecutor(可复用执行器,就是可复用 Statement 对象,减少系统开销用的,其他和简单执行器差不多)、BatchExecutor(批处理执行器)。

这边的话以 SimpleExecutor 执行器的源码进行分析,因为这篇是预编译的文章,所以题外话还是少说吧(虽然已经说很多了)。

SimpleExecutor 类中重写了俩核心方法:doUpdate和doQuery。
这俩核心方法都有相同的步骤就是(先是获取一个 StatementHandler 对象,再去调用 prepareStatement 方法):在这里插入图片描述
获取 StatementHandler 对象是通调用 Configuration.newStatementHandler 方法进行获取的,通过下面源码可以得知,得到的是一个 RoutingStatementHandler。
在这里插入图片描述接下来看看 RoutingStatementHandler 构造方法源码实现吧(可以看见内部封装了一个 StatementHandler 的委托者,从构造方法实现也可以看出这个 RoutingStatementHandler 就是起一个路由的作用,盲猜相关的执行靠的是内部封装的这个委托者,由于我们又知道默认的 StatementType 是 PREPARED,所以这里的内部委托执行器是 PreparedStatementHandler 对象):

	private final StatementHandler delegate;public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) {switch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}

那么再真正执行 sql 前的操作是什么就要看 prepareStatement 方法了。

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);// 获取connection对象stmt = handler.prepare(connection, transaction.getTimeout());// 进行预处理等操作handler.parameterize(stmt);// 预处理完后设置参数return stmt;}

那么接下来分析的就是 prepare 方法了,我们知道传过来的 StatementHandler 是一个 RoutingStatementHandler,但其内部是路由一个委托者去实现的即 PreparedStatementHandler。

在这里插入图片描述通过上面 prepare 方法的实现可以看出,实现方法在 BaseStatementHandler 中,

为了让你不烦迷糊,还是给个继承图吧:

在这里插入图片描述

下面给出 BaseStatementHandler 实现的 prepare 方法的源码:

在这里插入图片描述根据方法名可以判断出,主要步骤是在 instantiateStatement 方法中,为什么?因为它返回了一个 Statement 对象,我们知道 Mybatis 是对 JDBC 的封装,如果你熟知JDBC的6步走,那应该深知只有在获取数据库操纵对象的时候才会返回 Statement。

在这里插入图片描述
OK,我们找到 PreparedStatementHandler.instantiateStatement (实例化操纵对象方法),看看其源码实现。

在这里插入图片描述
分析到这就知道 Mybatis 是如何实现预编译的了吧,本质还是 JDBC 的 prepareStatement 去进行的预编译。同时通过源码分析我们也知道为什么 insert 语句中配置了 useGeneratedKeys="true" 会返回主键了。

三、总结

Mybatis 实现预编译主要是在执行 sql 前,会调用一个 prepareStatement 方法进行预处理,会传一个 StatementHandler 对象进去,本质是一个 RoutingStatementHandler,但其构造方法其实就是一个路由的作用,内部封装了一个委托者才是真正的执行者。

其委托者实现了 instantiateStatement (实例化 Statement) 方法。该方法就是 Mybatis 实现预编译的关键。prepareStatement方法会调用 BaseStatementHandler 中的 prepare 方法,然后会通过 instantiateStatement 方法返回一个 Statement 对象,即调用的是默认的 PreparedStatementHandler 中的方法,其本质呢就是 JDBC 中获取数据库操纵对象时进行的预编译处理一致,只是 Mybatis 对其进行了封装,为进行其他拓展…

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS中如何实现多列布局?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 多列布局(Multi-column Layout)⭐ column-count⭐ column-width⭐ column-gap⭐ column-rule⭐ column-span⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧…

JavaWeb_LeadNews_Day7-ElasticSearch, Mongodb

JavaWeb_LeadNews_Day7-ElasticSearch, Mongodb elasticsearch安装配置 app文章搜索创建索引库app文章搜索思路分析具体实现 新增文章创建索引思路分析具体实现 MongoDB安装配置SpringBoot集成MongoDB app文章搜索记录保存搜索记录思路分析具体实现 查询搜索历史删除搜索历史 搜…

时序分解 | MATLAB实现基于SGMD辛几何模态分解的信号分解分量可视化

时序分解 | MATLAB实现基于SGMD辛几何模态分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于SGMD辛几何模态分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SGMD分解算法(辛几何模态分解),分解结果可视…

再学http-为什么文件上传要转成Base64?

1 前言 最近在开发中遇到文件上传采用Base64的方式上传,记得以前刚开始学http上传文件的时候,都是通过content-type为multipart/form-data方式直接上传二进制文件,我们知道都通过网络传输最终只能传输二进制流,所以毫无疑问他们本…

超越界限:大模型应用领域扩展,探索文本分类、文本匹配、信息抽取和性格测试等多领域应用

超越界限:大模型应用领域扩展,探索文本分类、文本匹配、信息抽取和性格测试等多领域应用 随着 ChatGPT 和 GPT-4 等强大生成模型出现,自然语言处理任务方式正在逐步发生改变。鉴于大模型强大的任务处理能力,未来我们或将不再为每…

33、在SpringBoot项目添加Web组件(Servlet、Filter 和 Listener) 的三种方法

通过Spring Bean 来添加 Servlet、Filter 和 Listener ★ 添加Web组件(Servlet、Filter、Listener)的三种方式: - 使用Spring Bean添加Servlet、Filter或Listener。- 使用XxxRegistrationBean手动添加Servlet、Filter或Listener。- 使用Clas…

QT6安装完成后,再安装低版本的MinGW或其他组件方式

首先进入点击安装的uinstall Qt 并不是真的卸载 通过下面几步 1,首先登录自己账户 2,然后进入欢迎中,点击“添加和移除组件” 3,然后检索自己需要的安装内容

Unity实现广告滚动播放、循环播放、鼠标切换的效果

效果: 场景结构: 特殊物体:panel下面用排列组件horizent layout group放置多个需要显示的面板,用mask遮罩好。 using System.Collections; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using Unity…

第4篇:vscode+platformio搭建esp32 arduino开发环境

第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 第3篇:vscode搭建esp32 arduino开发环境 1.配置默认安装路径,安装到D盘。 打开环境变量,点击新建 输入变量名PLATFORMIO_CORE_DIR与路径:D:\PLATF…

linux服务TCP参数配置

Linux TCP参数配置 阿里云规范 1.【推荐】高并发服务器建议调小 TCP 协议的 time_wait 超时时间。 说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务器端会因为处于 time_wait 的连接数太多&am…

idea切换Git分支时保存未提交的文件

解决方案 我们现在有三个分支,如下图: 我们目前在tenant分支上进行开发,需要去修复master的Bug,假设我们在tenant分支上修改了一个文件,如下图: 方法一:使用Shelve Changes 1、选中tenant上你不…

高性能服务器Nodejs操作Mysql数据库

目录 1 Node 操作 mysql1.2 操作 mysql 数据库 2 Web 开发模式2.1 服务端渲染2.2 前后端分离2.3 如何选择 3 身份认证3.1 Session 认证机制3.2 JWT 认证机制 1 Node 操作 mysql 数据库和身份认证 配置 mysql 模块 安装 mysql 模块 npm install mysql建立连接 const mysql …

Android相机-HAL-Rockchip-hal3

引言: 对于Android相机的 HAL层而言对上实现一套Framework的API接口,对下通过V4L2框架实现与kernel的交互。不同的平台会有不同的实现方案。主要是对Android HAL3的接口的实现。看看rockchip是怎么支持hal3的? 代码目录: hardw…

python+tkinter实现多页面多菜单的demo实例

本篇文章主要讲解,python+tkinter多页面多菜单的demo实例,支持一个新窗口弹出、多页面切换,顶部菜单构建及事件绑定。 日期:2023年8月25日 版本:python3.9.6 实际效果 消息菜单-具体效果: 页面菜单具体效果: 事件菜单具体效果: 环境及依赖 python 3.9.6 依赖信息: …

rabbitMq安装后无法启动可视化页面http://localhost:15672处理

本次安装环境信息: 系统:win10 64位专业版 erlang:otp_win64_23.0 rabbitMQ:rabbitmq-server-3.8.5 安装rabbitMQ需要依赖erlang语言环境,所以需要我们下载erlang的环境安装程序。 一、下载安装程序 rabbitMQ安装…

java八股文面试[java基础]——接口和抽象类的区别

知识来源: 【基础】接口和抽象类_哔哩哔哩_bilibili 【2023年面试】Java中抽象类和接口有什么区别_哔哩哔哩_bilibili 【23版面试突击】抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么?_…

上门服务系统|上门服务小程序如何提升生活质量?

上门服务其实就是本地生活服务的升级,上门服务包含很多行业可以做的。例如:厨师上门、上门家电维修、跑腿等等。如今各类本地化生活服务越来越受大家的喜爱。基于此市场愿景,我们来谈谈上门服务系统功能。 一、上门服务系统功能 1、预约服务…

API 网关基础

目录 一、网关概述二、网关提供的功能三、常见网关系统3.1 Netflix Zuul3.2 Spring Cloud Gateway3.3 Kong3.4 APISIX3.5 Shenyu 一、网关概述 API网关是一个服务器,是系统的唯一入口。 从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部…

【SpringCloud技术专题】「Gateway网关系列」(1)微服务网关服务的Gateway组件的原理介绍分析

为什么要有服务网关? 我们都知道在微服务架构中,系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?难道要一个个的去调用吗?很显然这是不太实际的,我们需要有一个统一的接口与这些微服务打交道&#xf…