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

news2025/1/21 15:31:22

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.coloradmin.cn/o/925216.html

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

相关文章

车联网技术介绍

上图是目前车联网架构图,基于“云-管-端”的车联网系统架构以支持车联网应用的实现, “云”是指 V2X 基础平台、高基于精度定位平台等基础能力,可实现车辆动态厘米级定位,这将满足现阶段以及未来车联网应用场景的定位精度需求。 “…

【Linux网络】Cookie和session的关系

目录 一、Cookie 和 session 共同之处 二、Cookie 和 session 区别 2.1、cookie 2.2、session 三、cookie的工作原理 四、session的工作原理 一、Cookie 和 session 共同之处 Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式。 二、Cookie 和 session 区别 2.…

【C语言】基础知识杂记(整理自用)

前言 之前一直在学新知识,最近打算复习一下之前学的,所以写了这篇文章,记录一下不熟练的知识点,自用,对大家帮助可能不是很大。 double类型与float类型 编译器默认7.0为double类型 在数据后加一个f,编译…

Springboot+mybatis-plus+dynamic-datasource+Druid 多数据源 分布式事务

Springbootmybatis-plusdynamic-datasourceDruid 多数据源事务,分布式事务 文章目录 Springbootmybatis-plusdynamic-datasourceDruid 多数据源事务,分布式事务0.前言1. 基础介绍ConnectionFactoryAbstractRoutingDataSource 动态路由数据源的抽象类 Dyn…

mac地址、ip地址、子网掩码、端口

1. mac地址 又称为网络适配器或者网络接口卡NIC,但是现在更多人原因使用更简单的名称"网卡",通过网卡能够是不同的计算机之间相互连接,从而完成数据通信的功能 每一个网卡在出厂的时候 都会给分配到一个编号,类似与身份…

信创测试:创意与创新的评估之路

在当今竞争激烈的商业环境中,创意和创新成为企业脱颖而出的关键。为了有效评估和提升创意与创新的水平,信创测试被引入作为一个重要的工具。本文将介绍信创测试的概念、意义以及应用,探讨它在推动企业创新发展中的重要作用。 一、什么是信创测…

DOS常见命令

DOS常见命令 DOS是什么如何打开DOScmd常见的命令集合 DOS是什么 DOC命令是我们浏览器中的终端 ,但不同的是我们打开软件的方式 使用的是点击文件图标,点击图标的同时 我们也相当于使用一个命令 只是我们看不见而已 在电脑上操作的时候 通常都是使用命令…

Docker微服务实战

文章目录 业务需求IDEA编写代码编写Dockerfile构建镜像运行容器网页端访问测试 业务需求 利用Docker部署应用服务,实现在网页端通过输入地址 ip:端口/hello/docker,页面显示hello docker ! IDEA编写代码 创建springboot项目 网上很多教程,此步骤省略……

Mybatis动态之灵活使用下

目录 ​编辑 前言: 1.mybatis的分页 1.1分页的应用场景 1.2分页的使用方式 2.mybatis中特殊字符处理 2.1mybatis中特殊字符介绍 2.2mybatis中特殊字符的使用方式 前言: 上篇我已经写了Mybatis动态之灵活使用,接着上篇写mybatis的分页…

QT6串口模块QSerialport的安装,主要是“编译器”版本问题

参考文档 https://blog.csdn.net/lidandan2016/article/details/85929069 https://blog.csdn.net/qq_42968012/article/details/126020554 https://blog.csdn.net/weixin_48467622/article/details/119982667 整体测试解决步骤总结 首先,QT6都不能进行离线安装&a…

Cocos独立游戏开发框架中的计时器管理器

引言 本系列是《8年主程手把手打造Cocos独立游戏开发框架》,欢迎大家关注分享收藏订阅。在独立游戏开发中,计时器是一个至关重要的组件,用于管理时间相关的操作,如动画效果、技能冷却、任务进度等。然而,随着游戏变得…

【王道-绪论-计算机系统概述】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 知识框架No.1 操作系统的概念功能和定义一、操作系统的概念和定义二、操作系统的功能和目标1、资源的管理者2、向用户提供服务2.1图形化用户接口2.2联机命令接口2.3脱机命令接口2.4程序接口 3、对硬件机器的拓展 三、总结 No.2 操作系…

QT6新建工程方式导致pro文件和无pro文件,

QT6创建工程文件的不同 cmake创建的无.pro工程 qmake创建的有.pro文件的工程

VMware Workstation 不支持在此主机上使用虚拟化性能计数器。

目录 问题描述:VMware Workstation 不支持在此主机上使用虚拟化性能计数器。解决办法: 问题描述:VMware Workstation 不支持在此主机上使用虚拟化性能计数器。 打开其他地方移植过来的虚拟机失败 解决办法: 编辑虚拟机设置&am…

配置门槛这么低,Fooocus你值得拥有!

简介: Fooocus是一个图像生成软件,可以用于生成各种图像。 其主要特点是: 离线使用,不依赖云服务开源免费,代码开放自动化了许多内部优化,简化用户操作只需要关注提示和图像,实现人机交互安装简单,兼容性强硬件需求低,可以在普通笔记本上运行性能优异,速度很快内…

Linux socket网络编程概述 和 相关API讲解

socket网络编程的步骤 大体上,连接的建立过程就是:服务器在确定协议类型后,向外广播IP地址和端口号,并监听等待,直到客户端获取了IP地址和端口号并成功连接: 使用socket来进行tcp协议的网络编程的大体步骤…

53 个 CSS 特效 2

53 个 CSS 特效 2 这里是第 17 到 32 个,跟上一部分比起来多了两个稍微大一点的首页布局,上篇:53 个 CSS 特效 1,依旧,预览地址在 http://www.goldenaarcher.com/html-css-js-proj/,git 地址: …

Redis数据结构之Set

Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。相对于列表,集合也有两个特点:无序、不可重复 …

docker安装redis并持久化数据

1. 创建挂载目录 sudo mkdir -p /home/redis/conf sudo mkdir -p /home/redis/data sudo touch /home/redis/conf/redis.confcat > /home/redis/conf/redis.conf << EOF appendonly yes EOFrootk8s-master:/home/redis# ls conf data rootk8s-master:/home/redis# t…

基于SSM的小说网站的设计与实现(论文+源码)

目 录 1 绪论................................................................................................... 1 1.1 项目背景................................................................................................................ 1 1.2 发展历程..…