mybatis如何生成和执行动态sql

文章目录

  • 1. 相关代码
  • 2. SQL 语句解析流程
    • 2.1 XMLStatementBuilder
    • 2.2 SqlSource
    • 2.3 DynamicContext上下文
    • 2.4 SqlNode和组合模式
    • 2.5 MappedStatement
    • 2.6 解析标签
      • 2.6.1 \<include>
      • 2.6.2 \<selectKey>
      • 2.6.3 处理 SQL 语句
  • 3. 获取真正执行的sql

1. 相关代码

package com.boge.mapper;import com.boge.pojo.User;import java.util.List;public interface UserMapper {List<User> selectUserList(User user);User selectUserById(Integer id);int updateById(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.boge.mapper.UserMapper"><cache/><resultMap id="BaseResultMap" type="com.boge.pojo.User"><id property="id" column="id" jdbcType="INTEGER"/><result property="userName" column="user_name" jdbcType="VARCHAR"/><result property="realName" column="real_name" jdbcType="VARCHAR"/><result property="password" column="password" jdbcType="VARCHAR"/><result property="age" column="age" jdbcType="INTEGER"/><result property="dId" column="d_id" jdbcType="INTEGER"/></resultMap><sql id="baseSQL">id,user_name,real_name,password,age,d_id</sql><select id="selectUserById" resultType="com.boge.pojo.User">select<include refid="baseSQL"></include>fromt_userwhereid = #{id}</select><select id="selectUserList" resultMap="BaseResultMap">select<include refid="baseSQL"></include>fromt_user t<where><if test="userName != null and userName.trim() != ''">and t.user_name like concat('%', #{userName},'%')</if><if test="age != null">and t.age = {age}</if></where></select><update id="updateById">update t_user set user_name = #{userName} where id = #{id}</update></mapper>

2. SQL 语句解析流程

2.1 XMLStatementBuilder

  • XMLStatementBuilder。映射文件由<select>、<insert>、<delete>、<update>等标签是由XMLStatementBuilder.parseStatementNode()进行解析,不在由XMLMapperBuilder解析。

2.2 SqlSource

用来表示解析之后的sql语句。只定义一个方法getBoundSql() ,根据解析到的sql语句和入参生成一条可执行的sql。

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

核心实现:
在这里插入图片描述
核心类介绍:

  • DynamicSqlSource,当 SQL 语句中包含动态 SQL 和“${}”占位符的时候,会使用 DynamicSqlSource 对象。

判断一个 SQL 片段是否为动态 SQL,判断的标准是:如果这个 SQL 片段包含了未解析的“${}”占位符或动态 SQL 标签,则为动态 SQL 语句。但注意,如果是只包含了“#{}”占位符,也不是动态 SQL。
在这里插入图片描述

  • RawSqlSource,DynamicSqlSource 有两个不同之处:

    • RawSqlSource 处理的是非动态 SQL 语句,DynamicSqlSource 处理的是动态 SQL 语句;
    • RawSqlSource 解析 SQL 语句的时机是在初始化流程中,而 DynamicSqlSource 解析动态 SQL 的时机是在程序运行过程中,也就是运行时解析。
      在这里插入图片描述
  • StaticSqlSource, DynamicSqlSource 还是 RawSqlSource,底层都依赖 SqlSourceBuilder 解析之后得到的 StaticSqlSource 对象。StaticSqlSource 中维护了解析之后的 SQL 语句以及“#{}”占位符的属性信息(List 集合),其 getBoundSql() 方法是真正创建 BoundSql 对象的地方,这个 BoundSql 对象包含了上述 StaticSqlSource 的两个字段以及实参的信息。

2.3 DynamicContext上下文

在 MyBatis 解析一条动态 SQL 语句的时候,可能整个流程非常长,其中涉及多层方法的调用、方法的递归、复杂的循环等,其中产生的中间结果需要有一个地方进行存储,那就是 DynamicContext 上下文对象。

DynamicContext 的两个核心属性:

  • sqlBuilder 字段(StringJoiner 类型),用来记录解析之后的 SQL 语句(拼接sql)
  • bindings 字段,用来记录上下文中的一些 KV 信息(其实就是方法入参)

DynamicContext定义了内部类:ContextMap,ContextAccessor。

  1. ContextMap
    ContextMap继承HashMap,覆写了get()方法。
 @Overridepublic Object get(Object key) {String strKey = (String) key;if (super.containsKey(strKey)) {return super.get(strKey);}if (parameterMetaObject == null) {return null;}if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {return parameterMetaObject.getOriginalObject();} else {// issue #61 do not modify the context when readingreturn parameterMetaObject.getValue(strKey);}}
  1. ContextAccessor

DynamicContext 定义了一个静态代码块,指定了 OGNL 表达式读写 ContextMap 集合的逻辑,这部分读取逻辑封装在 ContextAccessor 中。除此之外,你还可以看到 ContextAccessor 中的 getProperty() 方法会将传入的 target 参数(实际上就是 ContextMap)转换为 Map,并先尝试按照 Map 规则进行查找;查找失败之后,会尝试获取“_parameter”对应的 parameterObject 对象,从 parameterObject 中获取指定的 Value 值。

2.4 SqlNode和组合模式

在 MyBatis 处理动态 SQL 语句的时候,会将动态 SQL 标签解析为 SqlNode 对象,多个 SqlNode 对象就是通过组合模式组成树形结构供上层使用的

接口定义:

public interface SqlNode {// apply()方法会根据用户传入的实参,解析该SqlNode所表示的动态SQL内容并// 将解析之后的SQL片段追加到DynamicContext.sqlBuilder字段中暂存。// 当SQL语句中全部的动态SQL片段都解析完成之后,就可以从DynamicContext.sqlBuilder字段中// 得到一条完整的、可用的SQL语句了boolean apply(DynamicContext context);}

在这里插入图片描述
1. StaticTextSqlNode 和 MixedSqlNode

StaticTextSqlNode 用于表示非动态的 SQL 片段,其中维护了一个 text 字段(String 类型),用于记录非动态 SQL 片段的文本内容,其 apply() 方法会直接将 text 字段值追加到 DynamicContext.sqlBuilder 的最末尾。

/*** @author Clinton Begin*/
public class StaticTextSqlNode implements SqlNode {private final String text;public StaticTextSqlNode(String text) {this.text = text;}@Overridepublic boolean apply(DynamicContext context) {context.appendSql(text);return true;}}

MixedSqlNode 在整个 SqlNode 树中充当树枝节点,也就是扮演组合模式中 Composite 的角色,其中维护了一个 List 集合用于记录 MixedSqlNode 下所有的子 SqlNode 对象。MixedSqlNode 对于 apply() 方法的实现也相对比较简单,核心逻辑就是遍历 List 集合中全部的子 SqlNode 对象并调用 apply() 方法,由子 SqlNode 对象完成真正的动态 SQL 处理逻辑。

/*** @author Clinton Begin*/
public class MixedSqlNode implements SqlNode {private final List<SqlNode> contents;public MixedSqlNode(List<SqlNode> contents) {this.contents = contents;}@Overridepublic boolean apply(DynamicContext context) {contents.forEach(node -> node.apply(context));return true;}
}

2. TextSqlNode
TextSqlNode 实现处理包含 “${}”占位符的动态 SQL 片段,将占位符替换为参数。

3. IfSqlNode
IfSqlNode 实现类处理动态 SQL 语句 <if>标签, <if>标签的test表达式,通过ExpressionEvaluator判断为真,继续执行子SqlNode 对象。ExpressionEvaluator的底层实现就是OGNL。

/*** @author Clinton Begin*/
public class IfSqlNode implements SqlNode {private final ExpressionEvaluator evaluator;private final String test;private final SqlNode contents;public IfSqlNode(SqlNode contents, String test) {this.test = test;this.contents = contents;this.evaluator = new ExpressionEvaluator();}@Overridepublic boolean apply(DynamicContext context) {if (evaluator.evaluateBoolean(test, context.getBindings())) {contents.apply(context);return true;}return false;}}

4. TrimSqlNode

TrimSqlNode 处理 MyBatis 动态 SQL 语句中的 <trim> 标签。

指定 prefix 和 suffix 属性添加前缀和后缀,也可以指定 prefixesToOverrides 和 suffixesToOverrides 属性来删除多个前缀和后缀(使用“|”分割不同字符串)

public class TrimSqlNode implements SqlNode {private final SqlNode contents;private final String prefix;private final String suffix;private final List<String> prefixesToOverride;private final List<String> suffixesToOverride;private final Configuration configuration;public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));}protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {this.contents = contents;this.prefix = prefix;this.prefixesToOverride = prefixesToOverride;this.suffix = suffix;this.suffixesToOverride = suffixesToOverride;this.configuration = configuration;}@Overridepublic boolean apply(DynamicContext context) {FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);boolean result = contents.apply(filteredDynamicContext);filteredDynamicContext.applyAll();return result;}...
}

5. WhereSqlNode和SetSqlNode
两者都继承了TrimSqlNode ,可以是处理特定类型的TrimSqlNode 。

public class WhereSqlNode extends TrimSqlNode {private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");public WhereSqlNode(Configuration configuration, SqlNode contents) {super(configuration, contents, "WHERE", prefixList, null, null);}}
/*** @author Clinton Begin*/
public class SetSqlNode extends TrimSqlNode {private static final List<String> COMMA = Collections.singletonList(",");public SetSqlNode(Configuration configuration,SqlNode contents) {super(configuration, contents, "SET", COMMA, null, COMMA);}}

6. ForEachSqlNode

处理<foreach>标签。

2.5 MappedStatement

用来表示解析之后的sql标签,包含sqlSource和sqlCommandType,分别记录了SQL 标签中定义的 SQL 语句和 SQL 语句的类型(INSERT、UPDATE、DELETE、SELECT 或 FLUSH 类型)。

public void parseStatementNode() {// 获取SQL标签的id以及databaseId属性String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");// 若databaseId属性值与当前使用的数据库不匹配,则不加载该SQL标签// 若存在相同id且databaseId不为空的SQL标签,则不再加载该SQL标签if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 获取节点的名称  select insert delete updateString nodeName = context.getNode().getNodeName();// 获取到具体的 sql 命令 类型SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsing  解析 include标签  替换include 标签  完成 ${} 解析XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}// 动态SQL的加载解析 同时记录了 sql中的占位符 ParameterMappingSqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String resultType = context.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");// >> 关键的一步: MappedStatement 的创建builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

2.6 解析标签

2.6.1 <include>

<include>作用是引入由<sql>标签定义的sql片段,<sql>实际在XMLMapperBuilder.sqlElement()解析。
在这里插入图片描述
<include> 标签由XMLIncludeTransformer.applyIncludes()处理,同时还会处理<include> 标签下的<property>标签和“${}"占位符。

2.6.2 <selectKey>

略。

2.6.3 处理 SQL 语句

  // 动态SQL的加载解析 同时记录了 sql中的占位符 ParameterMappingSqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

在这里插入图片描述

3. 获取真正执行的sql

在CachingExecutor.query()生成真正待执行的sql。

 @Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 获取SQLBoundSql boundSql = ms.getBoundSql(parameterObject);// 创建CacheKey:什么样的SQL是同一条SQL? >>// select * from t_user   select id,username,password form t_user// 根据特定的规则生成一个key 保证不冲突CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

进入getBoundSql(),

 public BoundSql getBoundSql(Object parameterObject) {// 拼接sqlBoundSql boundSql = sqlSource.getBoundSql(parameterObject);List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings == null || parameterMappings.isEmpty()) {boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);}// check for nested result maps in parameter mappings (issue #30)for (ParameterMapping pm : boundSql.getParameterMappings()) {String rmId = pm.getResultMapId();if (rmId != null) {ResultMap rm = configuration.getResultMap(rmId);if (rm != null) {hasNestedResultMaps |= rm.hasNestedResultMaps();}}}return boundSql;}

总结:通过SqlSource、DynamicContext和SqlNode的精妙配合,获得最终执行的sql。

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

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

相关文章

棒球在国际上的流行·棒球1号位

棒球在国际上的流行 1. 棒球的起源与历史 棒球的起源源于美国。19世纪中叶&#xff0c;由于美国领土的扩张&#xff0c;当时的美国殖民地的印第安人将棒球类游戏&#xff0c;带到了当时的弗吉尼亚州的奥克兰。后来&#xff0c;棒球运动流传到了加利福尼亚州的圣迭戈。早期的棒…

LeetCode--HOT100题(25)

目录 题目描述&#xff1a;141. 环形链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;141. 环形链表&#xff08;简单&#xff09; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连…

QtCreator ui设置界面 Layout 的属性 layoutStretch

layoutStretch 用于控制Layout在被用户进行缩放时。里面控件的缩放比例。如一个水平布局里面有两个控件 一个 QLineEdit 和 QPushButton。首先将两个控件的尺寸策列的水平策略都设置为Expanding。此时在将包含这两个控件的水平布局的 layoutStretch 进行如下设置。 运行程序就…

利用python实现网络设备配置批量上传和批量下载功能

利用python实现网络设备配置批量上传和批量下载功能 利用ensp实现网络设备和物理主机互通配置网络设备配置批量上传功能配置批量下载功能常见问题 提示&#xff1a; 本文章代码所使用目录均使用相对目录&#xff0c;只需将配置存放目录和文件下载目录&#xff08;已用符号标出…

8.利用matlab完成 符号微积分和极限 (matlab程序)

1.简述 一、符号微积分 微积分的数值计算方法只能求出以数值表示的近似解&#xff0c;而无法得到以函数形式表示的解析解。在 MATLAB 中&#xff0c;可以通过符号运算获得微积分的解析解。 1. 符号极限 MATLAB 中求函数极限的函数是 limit&#xff0c;可用来求函数在指定点的…

Node.js新手在哪儿找小项目练手?

前言 可以参考一下下面的nodejs相关的项目&#xff0c;希望对你的学习有所帮助&#xff0c;废话少说&#xff0c;让我们直接进入正题>> 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数…

数据结构-队列(C语言的简单实现)

简介 队列也是一种数据结构&#xff0c;队列也可以用来存放数字每次只能向队列里将入一个数字&#xff0c;每次只能从队列里获得一个数字在队列中&#xff0c;允许插入的一段称为入队口&#xff0c;允许删除的一段称为出队口它的原则是先进先出(FIFO: first in first out)&…

Titanic--细节记录二

目录 merge、join以及concat的方法的不同以及相同 merge join concat stack函数 agg函数 countplot--计算条形统计图 FacetGrid kdeplot--核密度估计图 facet.set facet.add_legend() 折线图表示年龄分布情况 为什么所有的曲线都被添加到同一个图上&#xff1a; 填充…

标记垃圾,有三种色彩:四千长文带你深入了解三色标记算法

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

MFC计算分贝

分贝的一种定义是&#xff0c;表示功率量之比的一种单位&#xff0c;等于功率强度之比的常用对数的10倍&#xff1b; 主要用于度量声音强度&#xff0c;常用dB表示&#xff1b; 其计算&#xff0c;摘录网上一段资料&#xff1b; 声音的分贝值可以通过以下公式计算&#xff1…

【数据结构】‘双向链表’冲冲冲

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Mybatis-Plus

1. Mybatis-Plus概念 1.1 Mybatis-Plus介绍 官⽹&#xff1a; https://mybatis.plus/ 或 https://mp.baomidou.com/ Mybatis-Plus 介绍 MyBatis-Plus &#xff08;简称 MP &#xff09;是⼀个 MyBatis 的增强⼯具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;…

“可一学院”区块链学习平台正式启动,助力BSV技术普及与传播

2023年8月8日&#xff0c;上海可一澈科技有限公司&#xff08;以下简称“可一科技”&#xff09; 正式发布区块链学习平台“可一学院”。“可一学院” 立足于BSV区块链技术本源&#xff0c;汇集了多层次的专业课程和学习资源&#xff0c;致力于打造一个适合各类人群使用的一站式…

SpringMVC关于SSM的整合配置步骤

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaweb 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 SSM整合 一、创建工程1.1创建Maven工程1.2工程命名1.3检查…

Spring Boot 项目实现 Spring AOP

【注】实现在SpringBoot项目中&#xff0c;同时给两个类的方法添加AOP前置通知 1、创建一个SpringBoot项目 2、创建两个目标类和方法 package com.tqazy.learn_spring_project.spring_aop;import org.springframework.stereotype.Service;/*** ClassName SpringAopUserServi…

JZ40最小的K个数

题目地址&#xff1a;最小的K个数_牛客题霸_牛客网 题目回顾&#xff1a; 解题思路&#xff1a; 注意本题不需要去重。 最简单的方法&#xff1a;排序后数组顺序是由小到大的&#xff0c;也就是说此时数组前k个数就是我们要求的结果。 整体代码&#xff1a; public ArrayLi…

【Linux从入门到精通】文件I/O操作(C语言vs系统调用)

文章目录 一、C语言的文件IO相关函数操作 1、1 fopen与fclose 1、2 fwrite 1、3 fprintf与fscanf 1、4 fgets与fputs 二、系统调用相关接口 2、1 open与close 2、2 write和read 三、简易模拟实现cat指令 四、总结 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x1f64b;‍…

MySQL多表查询

1.创建student和score表 创建score表 2.为student表和score表增加记录 向student表插入记录的INSERT语句如下&#xff1a; 向score表插入记录的INSERT语句如下&#xff1a; 1.查询student表的所有记录 2.查询student表的第2条到4条记录 3.从student表查询所有学生的学号&#…

图·c++

数据结构&#xff1a; 邻接矩阵&#xff0c;邻接表 1.图的存储方式&#xff1a;邻接矩阵&#xff0c;邻接表 1.稀疏图和稠密图 2.无向图&#xff1a; n n n 个点&#xff0c;最多 n ( n − 1 ) / 2 n(n-1)/2 n(n−1)/2 条边&#xff0c; 当 m m m 接近 n ( n − 1 ) / 2 …

【JVM】CPU飙高排查方案与思路

文章目录 CPU飙高排查方案与思路 CPU飙高排查方案与思路 1.使用top命令查看占用cpu的情况 2.通过top命令查看后&#xff0c;可以查看是哪一个进程占用cpu较高&#xff0c;上图所示的进程为&#xff1a;40940 3.查看进程中的线程信息 4.可以根据进程 id 找到有问题的线程&a…