Mybatis解析动态sql原理分析

MyBatis第七讲:MyBatis动态SQL优雅实践与原理 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。 阅读详情

https://www.cnblogs.com/fangjian0423/p/mybaits-dynamic-sql-analysis.html

前言

废话不多说,直接进入文章。
我们在使用mybatis的时候,会在xml中编写sql语句。
比如这段动态sql代码:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

mybatis底层是如何构造这段sql的?
这方面的知识网上资料不多,于是就写了这么一篇文章。
下面带着这个疑问,我们一步一步分析。

介绍MyBatis中一些关于动态SQL的接口和类

SqlNode接口,简单理解就是xml中的每个标签,比如上述sql的update,trim,if标签:

public interface SqlNode {
  boolean apply(DynamicContext context);
}

SqlSource Sql源接口,代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等:

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}

BoundSql类,封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数:

XNode,一个Dom API中的Node接口的扩展类。

BaseBuilder接口及其实现类(属性,方法省略了,大家有兴趣的自己看),这些Builder的作用就是用于构造sql:

下面我们简单分析下其中4个Builder:

1 XMLConfigBuilder

解析mybatis中configLocation属性中的全局xml文件,内部会使用XMLMapperBuilder解析各个xml文件。

2 XMLMapperBuilder

遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部会使用XMLStatementBuilder处理xml中的每个节点。

3 XMLStatementBuilder

解析xml文件中各个节点,比如select,insert,update,delete节点,内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会丢到Configuration的mappedStatements中。

4 XMLScriptBuilder

解析xml中各个节点sql部分的Builder。

LanguageDriver接口及其实现类(属性,方法省略了,大家有兴趣的自己看),该接口主要的作用就是构造sql:

简单分析下XMLLanguageDriver(处理xml中的sql,RawLanguageDriver处理静态sql):

XMLLanguageDriver内部会使用XMLScriptBuilder解析xml中的sql部分。

ok, 大部分比较重要的类我们都已经介绍了,下面源码分析走起。

源码分析走起

Spring与Mybatis整合的时候需要配置SqlSessionFactoryBean,该配置会加入数据源和mybatis xml配置文件路径等信息:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatisConfig.xml"/>
    <property name="mapperLocations" value="classpath*:org/format/dao/*.xml"/>
</bean>

我们就分析这一段配置背后的细节:

SqlSessionFactoryBean实现了Spring的InitializingBean接口,InitializingBean接口的afterPropertiesSet方法中会调用buildSqlSessionFactory方法

buildSqlSessionFactory方法内部会使用XMLConfigBuilder解析属性configLocation中配置的路径,还会使用XMLMapperBuilder属性解析mapperLocations属性中的各个xml文件。

部分源码如下:

由于XMLConfigBuilder内部也是使用XMLMapperBuilder,我们就看看XMLMapperBuilder的解析细节。

我们关注一下,增删改查节点的解析。

XMLStatementBuilder的解析:

默认会使用XMLLanguageDriver创建SqlSource(Configuration构造函数中设置)。

XMLLanguageDriver创建SqlSource:

XMLScriptBuilder解析sql:

得到SqlSource之后,会放到Configuration中,有了SqlSource,就能拿BoundSql了,BoundSql可以得到最终的sql。

实例分析

我以以下xml的解析大概说下parseDynamicTags的解析过程:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

在看这段解析之前,请先了解dom相关的知识,xml dom知识, dom博文

parseDynamicTags方法的返回值是一个List,也就是一个Sql节点集合。SqlNode本文一开始已经介绍,分析完解析过程之后会说一下各个SqlNode类型的作用。

1 首先根据update节点(Node)得到所有的子节点,分别是3个子节点

(1)文本节点 \n UPDATE users

(2)trim子节点 ...

(3)文本节点 \n where id = #{id}

2 遍历各个子节点

(1) 如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode

(2) 如果节点类型是元素,说明该update节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点。这里的NodeHandler是XMLScriptBuilder的一个内部接口,其实现类包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看类名也就明白了这个Handler的作用,比如我们分析的trim节点,对应的是TrimHandler;if节点,对应的是IfHandler...

这里子节点trim被TrimHandler处理,TrimHandler内部也使用parseDynamicTags方法解析节点

3 遇到子节点是元素的话,重复以上步骤

trim子节点内部有7个子节点,分别是文本节点、if节点、是文本节点、if节点、是文本节点、if节点、文本节点。文本节点跟之前一样处理,if节点使用IfHandler处理

遍历步骤如上所示,下面我们看下几个Handler的实现细节。

IfHandler处理方法也是使用parseDynamicTags方法,然后加上if标签必要的属性。

private class IfHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
}

TrimHandler处理方法也是使用parseDynamicTags方法,然后加上trim标签必要的属性。

private class TrimHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      String prefix = nodeToHandle.getStringAttribute("prefix");
      String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      String suffix = nodeToHandle.getStringAttribute("suffix");
      String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
      TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      targetContents.add(trim);
    }
}

以上update方法最终通过parseDynamicTags方法得到的SqlNode集合如下:

trim节点:

由于这个update方法是个动态节点,因此构造出了DynamicSqlSource。

DynamicSqlSource内部就可以构造sql了:

DynamicSqlSource内部的SqlNode属性是一个MixedSqlNode。

然后我们看看各个SqlNode实现类的apply方法

下面分析一下两个SqlNode实现类的apply方法实现:

MixedSqlNode:

public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
}

MixedSqlNode会遍历调用内部各个sqlNode的apply方法。

StaticTextSqlNode:

public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
}

直接append sql文本。

IfSqlNode:

public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
}

这里的evaluator是一个ExpressionEvaluator类型的实例,内部使用了OGNL处理表达式逻辑。

TrimSqlNode:

public boolean apply(DynamicContext context) {
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    boolean result = contents.apply(filteredDynamicContext);
    filteredDynamicContext.applyAll();
    return result;
}

public void applyAll() {
  sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
  String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
  if (trimmedUppercaseSql.length() > 0) {
    applyPrefix(sqlBuffer, trimmedUppercaseSql);
    applySuffix(sqlBuffer, trimmedUppercaseSql);
  }
  delegate.appendSql(sqlBuffer.toString());
}

private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
  if (!prefixApplied) {
    prefixApplied = true;
    if (prefixesToOverride != null) {
      for (String toRemove : prefixesToOverride) {
        if (trimmedUppercaseSql.startsWith(toRemove)) {
          sql.delete(0, toRemove.trim().length());
          break;
        }
      }
    }
    if (prefix != null) {
      sql.insert(0, " ");
      sql.insert(0, prefix);
    }
  }
}

TrimSqlNode的apply方法也是调用属性contents(一般都是MixedSqlNode)的apply方法,按照实例也就是7个SqlNode,都是StaticTextSqlNode和IfSqlNode。 最后会使用FilteredDynamicContext过滤掉prefix和suffix。

总结

大致讲解了一下mybatis对动态sql语句的解析过程,其实回过头来看看不算复杂,还算蛮简单的。 之前接触mybaits的时候遇到刚才分析的那一段动态sql的时候总是很费解。

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

想搞明白这个trim节点的prefixOverrides到底是什么意思(从字面上理解就是前缀覆盖),而且官方文档上也没这方面知识的说明。我将这段xml改成如下:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            , name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

(第二段第一个if节点多了个逗号) 结果我发现这2段xml解析的结果是一样的,非常迫切地想知道这到底是为什么,然后这也促使了我去看源码的决心。最终还是看下来了。

文章有点长,而且讲的也不是非常直观,希望对有些人有帮助。

双目相机模型 近期想研究一下 双目相机 的内容,故先把 理论 弄清楚! 一、双目相机模型 针孔相机模型描述了 单个相机 的成像模型。然而,仅根据一个像素,我们是无法确定 这个空间点的具体位置的。这是因为,从相机光心到归一化平面连线上的所有点,都可以 投影至该像素上。只有当 P 的深度确定时(比如通过双目或 RGB-D 相机),我们才能确 切地知道它的空间位置。 测量像素距离(或深度)的方式有很多种,像人眼就可以根据左右眼看到的景物差异 (或称视差)来判断物体与我们的距离。双目相机的原理亦是如.. 阅读详情

相关推荐

一款值得关注的开源企业管理ERP系统

盒木ERP是一款开源的现代化企业管理系统,采用前后端分离架构,后端基于Python+Django,前端使用Vue+Ant Design。系统涵盖采购、销售、库存、财务等核心业务模块,支持移动端操作和PDA扫码。具有开源免费、技术先进、功能完善等特点,适合中小企业使用或二次开发。项目提供详细部署指南,支持Docker容器化部署,是学习ERP系统开发和企业数字化转型的优秀选择。

1104

Mybatis自定义拦截器与插件开发

在Spring中我们经常会使用到拦截器,在登录验证、日志记录、性能监控等场景中,通过使用拦截器允许我们在不改动业务代码的情况下,执行拦截器的方法来增强现有的逻辑。在mybatis中,同样也有这样的业务场景,有时候需要我们在不侵入原有业务代码的情况下拦截sql,执行特定的某些逻辑。那么这个过程应该怎么实现呢,同样,在mybatis中也为开发者预留了拦截器接口,通过实现自定义拦截器这一功能,可以实现我们自己的插件,允许用户在不改动mybatis的原有逻辑的条件下,实现自己的逻辑扩展。 那么,在实现拦截器之前,首

1561

【网络教程】Ubuntu有线连接消失,无法联网怎么办!(亲测有效!)

文章目录问题描述解决方案 问题描述 今天打开Ubuntu,突然发现无法连接网络,并且打开设置,发现设置里面的网络已经没有了有线连接,只有VPN设置选项,如图 解决方案 按照下列的代码执行即可! # 关闭网路 service NetworkManager stop # 删除NetworkManager缓存文件 sudo rm /var/lib/NetworkManager/NetworkManager.state # 打开网络 service NetworkManager start 运行完

XiaoqiangClub的博客 1万+

MyBatis - 分页之四大方式(数组、SQL、拦截器,RowBounds)

前言:学习hibernate &amp; mybatis等持久层框架的时候,不外乎对数据库的增删改查操作。而使用最多的当是数据库的查找操作, 而当数据库数据过多时,符合查找条件的数据可能也会是很庞大的数据。往往在这个时候,我们都不会希望一次性的将所有的数据一起性读取出来,并且显示在UI界面上。常用的操作,就是对查找到的数据进行分页,每次处理小部分数据。这样每次处理的数据量就会在可控的范围,UI的...

4129

iqoo11如何获取root权限?

🏆本文收录于专栏,主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家!持续更新中,up!up!up!!

**My Coding Family** 5809

手把手完整实现STM32+ESP8266+MQTT+阿里云服务器+手机APP应用——第一节—阿里云物联网平台配置+IoTStudio界面绘制+MQTT.fx模拟MQTT客户端与使用AT命令发布订阅消息

通过MQTT.fx模拟连接以及通过CH340串口连接ESP8266发送AT命令,实现阿里云物联网平台发送数据同时接收数据,IOT studio界面显示数据。具体来说:使用ESP8266(本系列使用了ESP8266-ESP01/01S以及正点原子的ATK-ESP8266) 来连接网络,获取设备数据发送到阿里云物联网平台并显示数据,通过IOT studio界面显示数据并发送命令控制设备。本系列使用的主控芯片是STM32F103系列,当然也可以使用其他系列单片机都可以实现,思路都是一样的。同时本系列有手机APP源

嵌入式阿齐Archie 5万+

MyBatis Plus updateById方法更新不了空字符串null

背景 在更新数据时使用updateById方法更新数据,但是发现为空的默认没有更新,在网上和官网搜索的许多方法都没有实现,最后查看底层实现才弄出来了,我使用的版本是MyBatis Plus3.3.0 解决方案 在实体类中,属性值上加上注解: @TableField(value = "STUID", updateStrategy = FieldStrategy.IGNORED, jdbcType = JdbcType.BIGINT) private Long stuid; ...

dinghongyu520的博客 523

GD32F4xx 移植agile_modbus软件包与电能表通信

GD32F4XX 移植agile modbus软件包实现与DTSU电能表通信调试记录

madao1234的博客 1303

mybatis(4)Mybatis解析动态sql原理分析

出自:https://www.cnblogs.com/fangjian0423/p/mybaits-dynamic-sql-analysis.html Mybatis解析动态sql原理分析 前言 废话不多说,直接进入文章。 我们在使用mybatis的时候,会在xml中编写sql语句。 比如这段动态sql代码: <update id="update" parameterType="org....

cowbin2012的专栏 1万+

基于滤波反投影的图像重建算法matlab仿真

基于滤波反投影的图像重建算法是一种用于医学影像重建的计算机辅助技术。该算法可以从投影数据中重建出高质量的三维图像,被广泛应用于医学影像学、CT、PET等领域。本文将详细介绍基于滤波反投影的图像重建算法的原理、步骤和应用。一、原理介绍基于滤波反投影的图像重建算法基于X射线CT成像原理,它的主要思想是通过对投影数据进行处理,反推出被成像物体的三维形状。其核心原理是投影数据与目标物体之间的线性关系,即通过估计目标物体的吸收率来计算投影数据的变化,从而反推出目标物体的三维形状。

平时工作较为繁忙,私信消息一般晚上回复,谢谢大家~~ 385

【S32K3 MCAL配置】-3.4-CANFD配置-接收特定的“经典CAN/CANFD标准帧”和“经典CAN/CANFD扩展帧”(基于MCAL+FreeRTOS)

【S32K3 MCAL配置】-3.4-CANFD配置-接收特定的“经典CAN/CANFD标准帧”和“经典CAN/CANFD扩展帧”(基于MCAL+FreeRTOS)

「汽车电子助手」的博客 694

mysql动态sql原理_Mybatis解析动态sql原理分析

1.MyBatis一般使用步骤1.1获取Configuration实例或编写配置文件//获取Configuration实例的样例TransactionFactory transactionFactory = new JdbcTransactionFactory();//定义事务工厂Environment environment =new Environment("development", tra...

weixin_32529429的博客 510

国科大学习资料--机器人智能控制_袁如意老师PPT后习题答案(学长精心整理)

人工神经元激活函数的作用是什么? 答:激活函数是用来加入非线性因素的,提高神经网络对模型的表达能力,解决线性模型所不能解决的问题。 人工神经网络的学习规则有哪些?各有何特征? 答:大类可以分为无监督学习算法、有监督学习算法和强化学习。其中无监督学习算法指只有输入信息,没有期望的输出信息,如Hebb学习规则,神经元连接强度的变化与两个相互连接的神经元的激活水平成正比。竞争学习法则:网络各输出单元相互竞争,最后达到只有一个最强者激活。有监督学习算法指同时提供输入信息和对应的期望输出信息,如误差学习(Delta学

无名何许人的博客 1486

【MyBatis】MyBatis解析动态SQL原理分析

MyBatis通过SqlNode、SqlSource等处理动态SQL,XMLMapperBuilder解析XML,NodeHandler(如IfHandler、TrimHandler)构造动态SQL逻辑,最终生成BoundSql执行。prefixOverrides用于trim标签中忽略特定前缀,精简SQL输出。

小七的博客 1576

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

MyBatis解析动态SQL原理分析

废话不多说,直接进入文章。 我们在使用mybatis的时候,会在xml中编写sql语句。 比如这段动态sql代码: <update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> UPDATE users <trim prefix="SET" prefixOverrides=","> <if test="name != null and n...

Xx__WangQi的博客 1226

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

mybatis实现原理解析(动态sql、动态代理、缓存等分析)

一、mybatis操作数据库基本流程 1、读取配置文件信息 InputStream resource = Resources.getResourceAsStream("sqlMapConfig.xml"); 2、获取连接工厂 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resource); 3、打开连接 SqlSession session = sessionFa

li210530的博客 561

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Mybatis解析动态sql原理分析(搬运)

目录1、首先,附上原文链接:2、介绍MyBatis中一些关于动态SQL的接口和类3、源码分析走起4、实例分析5、总结 1、首先,附上原文链接: https://www.cnblogs.com/fangjian0423/p/mybaits-dynamic-sql-analysis.html 废话不多说,直接进入文章。 我们在使用mybatis的时候,会在xml中编写sql语句。 比如这段动态sql代码: <update id="update" parameterType="org.format.dyna

JoyceYoung的博客 623

MyBatis源码解析 ---- MyBatis动态SQL底层原理分析

前言 废话不多说,直接进入文章。 我们在使用mybatis的时候,会在xml中编写sql语句。 比如这段动态sql代码: <update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> UPDATE users <trim prefix="SET" prefixOverrides=","> <if test="name != null and na

lhl_2010的博客 396

通过源码分析MyBatis的缓存/Mybatis解析动态sql原理分析

通过源码分析MyBatis的缓存 http://www.cnblogs.com/fangjian0423/p/mybatis-cache.html Mybatis解析动态sql原理分析 http://www.cnblogs.com/fangjian0423/p/mybaits-dynamic-sql-analysis.html

ice-wee的专栏 481

MyBatis源码解析 ---- MyBatis动态SQL底层原理分析

转自 http://format-blog-image.qiniudn.com/dynamicsql2.png前言废话不多说,直接进入文章。我们在使用mybatis的时候,会在xml中编写sql语句。比如这段动态sql代码:123456789101112131415&lt;update id="update" parameterType="org.format.dynamicproxy.mybat...

1 5405

Mybatis动态SQL原理解析

资料准备 官网 首先翻阅官网文档,(https://mybatis.org/mybatis-3/dynamic-sql.html)这页文档首先描述了Mybatis的动态SQL是多么牛逼,解决了哪些问题,又讲了如何使用,而我们想知道他是如何实现的,所以这不是我们想要的。 Dynamic SQL One of the most powerful features of MyB...

一瓶绿茶三元钱 3228
上一篇: 通过源码分析MyBatis的缓存
下一篇: Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析
god_love_hlq_xff
博客等级 码龄11年 27粉丝 37原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值