返回顶部
首页 > 资讯 > 精选 >Mybatis中SQL节点实例分析
  • 887
分享到

Mybatis中SQL节点实例分析

2023-06-29 14:06:44 887人浏览 泡泡鱼
摘要

这篇文章主要讲解了“mybatis中sql节点实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Mybatis中SQL节点实例分析”吧!一、文章引出原因某天在完成项目中的一个小功能后进行

这篇文章主要讲解了“mybatissql节点实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Mybatis中SQL节点实例分析”吧!

一、文章引出原因

某天在完成项目中的一个小功能后进行自测的时候,发现存在一个很奇怪的 bug --- 最终执行的 SQL 与我所期望的 SQL 不一致,有一个 if 分支在我不传特定参数的情况下被拼接在最终的 SQL 上。

①定义在 XML 文件中的 SQL 语句

<select id="balanceByUserIds" parameterType="xxx.BalanceReqVO" resultType="xxx.Balance">        select * from balance        <where>            <if test="dataOrGCodes != null and dataOrgCodes.size > 0">                and data_org_code in                <foreach collection="dataOrgCodes" open="(" separator="," close=")" item="dataOrgCode">                    #{dataOrgCode}                </foreach>            </if>            <if test="dataOrgCode != null and dataOrgCode != ''">                and data_org_code = #{dataOrgCode}            </if>        </where>    </select>

②传进来的参数

{    "dataOrgCodes":["6","2"]}

③Mybatis 打印执行的 SQL

SELECT*FROMbalanceWHEREdata_org_code IN (?, ?)AND data_org_code = ?

打印的执行参数

{    "dataOrgCodes":["6","2"]}

二、存在的问题

学过 Mybatis 的人应该一样就看出来了,这个 SQL 不对劲,多了一些不该有的东西。按照我们的理解,最终的执行的 SQL 应该是

SELECT*FROMbalanceWHEREdata_org_code IN (?, ?)

但 mybatis 执行的 SQL 多了一点语句---AND data_org_code = ?

在出现这个问题后我反复进行 debug,确定了自己传进来的参数没有什么问题,也没有什么拦截器添加多余的参数。

三、分析 SQL 生成过程

在确定编写 XML 文件的 if 标签的内容以及传进来的参数无误后,排除了参数导致问题。那么除了这个可能外,问题就可能出现在 SQL 的解析上,也就是 SQL 的生成那里。那么我们定位到 SQL 的生成地方, DynamicSqlSource#getBoundSql(我们查询的参数对象)方法

// Configuration是Mybatis核心类,rootSqlnode 根SQL节点是我们定义在XML中的SQL语句。//(例如<select>rootSqlNode</sselect>, 标签中间的内容就是 rootSqlNode)public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {    this.configuration = configuration;    this.rootSqlNode = rootSqlNode;}public BoundSql getBoundSql(Object parameterObject) {  DynamicContext context = new DynamicContext(configuration, parameterObject);  rootSqlNode.apply(context);  ..............................    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);  context.getBindings().forEach(boundSql::setAdditionalParameter);  return boundSql;}

可以看到方法内部显示创建了一个 DynamicContext,这个对象就是用于存储动态生成的 SQL。

(下面是省略了很多关于本次问题无关的代码,只保留有关代码)

public class DynamicContext {  public static final String PARAMETER_OBJECT_KEY = "_parameter";  public static final String DATABASE_ID_KEY = "_databaseId";  // 存储动态生成的SQL,类似于 StringBuilder 的角色  private final StringJoiner sqlBuilder = new StringJoiner(" ");  // 唯一编号值,会在生成最终SQL和参数值映射关系的时候用到  private int uniqueNumber = 0;  // 拼接SQL  public void appendSql(String sql) {    sqlBuilder.add(sql);  }  // 获取拼接好的SQL  public String getSql() {    return sqlBuilder.toString().trim();  }  // 获取唯一编号,返回后进行加一  public int getUniqueNumber() {    return uniqueNumber++;  }}

而下一句就是解析我们编写的 SQL,完成 SQL 的拼接

rootSqlNode.apply(context)

这里的 rootSqlNode 是我们编写在标签里的 SQL 内容,包括<if>、<foreach>、<where>标签等内容。

rootSqlNode 对象是 SqlNode 类型。其实这里的 SQL 语句被解析成类似于 html 的 DOM 节点的树级结构,在本节的测试例子中结构类似如下(不完全正确,只做参考价值,表示 rootSqlNode 结构类似于以下结构):

<SqlNode>  select * from balance    <SqlNode>        where        <SqlNode>            and data_org_code in            <SqlNode>               #{dataOrgCode}            </SqlNode>        </SqlNode>      <SqlNode>            and data_org_code =            <SqlNode>               #{dataOrgCode}            </SqlNode>        </SqlNode>    </SqlNode></SqlNode>

这个 SqlNode 定义如下所示:

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

里面的 apply 方法是用于评估是否把这个 SqlNode 的内容拼接到最终返回的 SQL 上的,不同类型的 SqlNode 有不同的实现,例如我们本节相关的 SqlNode 类型就是为 IfSqlNode,对应这我们写的 SQL 语句的 if 标签,以及存储最终的 sql 内容的 StaticTextSqlNode 类型。

public class StaticTextSqlNode implements SqlNode {  // 存储我们写的 sql   // 类似于 and data_org_code in  private final String text;  public StaticTextSqlNode(String text) {    this.text = text;  }  @Override  public boolean apply(DynamicContext context) {    // 调用 DynamicContext 对象的 sqppendSql 方法拼接最终 sql    context.appendSql(text);    return true;  }}
public class IfSqlNode implements SqlNode {  // 评估器  private final ExpressionEvaluator evaluator;  // if标签中用于判断这个语句是否生效的 test 属性值  // 这里对应我们例子中的一个为 "dataOrgCodes != null and dataOrgCodes.size > 0"  private final String test;  // if标签中的内容,如果if标签中不存在其他标签,那么这里的值就是StaticTextSqlNode类型的节点  // StaticTextSqlNode 节点的 text 属性就是我们最终需要拼接的 sql 语句  private final SqlNode contents;  // contents 是我们定义在 if 标签里面的内容, test 是 if 标签的属性 test 定义的内容  public IfSqlNode(SqlNode contents, String test) {    this.test = test;    this.contents = contents;    this.evaluator = new ExpressionEvaluator();  }  @Override  public boolean apply(DynamicContext context) {    // 使用评估器评估 if 标签中定义的 test 中的内容是否为true    if (evaluator.evaluateBoolean(test, context.getBindings())) {      // 当contents为StaticTextSqlNode类型的节点时候,就把 if 标签里的内容拼接到 sql 上      // 否则继续调用方法 apply(相当于递归调用,知道找到最下面的内容节点)      contents.apply(context);      return true;    }    return false;  }}

我们可以看到这里的

evaluator.evaluateBoolean(test, context.getBindings())

这个评估方法是通过把 test 语句内容和 我们传进来的参数解析出来的 Map 进行比对,如果我们的参数中存在值,且值得内容符合 test 语句的判断,则进行 sql 语句的拼接。例如本次例子中的

<if test="userIds != null and userIds.size > 0">    and data_org_code in    <foreach collection="dataOrgCodes" open="(" separator="," close=")" item="dataOrgCode">         #{dataOrgCode}    </foreach></if>

以及我们传进来的参数进行比对

{    "dataOrgCodes":["6","2"]}

可以看得出来参数与 test 语句 "dataOrgCodes!= null and dataOrgCodes.size > 0" 比较是返回 true 的。

四、分析多余 SQL 的生成

根据上面的执行步骤可以知道,我们的 bug 的产生是在

evaluator.evaluateBoolean(test, context.getBindings()) 这一步产生的。也就是在 context.getBindings() 中存在满足 dataOrgCode != null and dataOrgCode != '' 的属性。debug 验证以下可知

Mybatis中SQL节点实例分析

可以看得出来,存储参数映射的 Map 出现了 dataOrgCode 的属性,但是我们传递进来的属性只有 dataOrgCodes 数组,没有 dataOrgCode 属性,那这个 dataOrgCode 属性是怎么来的?

再次从头进行 debug 发现问题出现在 ForEachSqlNode 的 apply 方法里面

Mybatis中SQL节点实例分析

public boolean apply(DynamicContext context) {  // 获取参数映射存储Map  Map<String, Object> bindings = context.getBindings();  // 获取bingdings中的parameter参数,key为collectionExpression,也就是我们写在标签foreach 标签的 collection 值里的内容  // 根据collectionExpression从参数映射器中获取到对应的值, 本次的值为:["1","2"]  final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings,    Optional.ofNullable(nullable).orElseGet(configuration::isNullableOnForEach));  if (iterable == null || !iterable.iterator().hasNext()) {    return true;  }  // 第一个参数  boolean first = true;  // 再拼接sql里添加我们定义在 foreach 标签的 open 值里的内容  applyOpen(context);  // 遍历的计数器  int i = 0;  // 遍历我们传进来的数组数据 ["1","2"]  // o 表示我们本次遍历数组中的值,例如 ”1“  for (Object o : iterable) {    DynamicContext oldContext = context;    if (first || separator == null) {      context = new PrefixedContext(context, "");    } else {      context = new PrefixedContext(context, separator);    }    int uniqueNumber = context.getUniqueNumber();    // 把 foreach 标签的 index 值里的内容作为 key,计数器的值 i 作为 value 存储到 bingdings 中。    // 例如第一次循环就为("index",0)。注意:由于相同的key会被覆盖住,所以最终存储的为("index",userIds.length - 1)    // 同时生成一个 key 为 ITEM_PREFIX + index 值内容 + "_" + uniqueNumber,value 为 uniqueNumber 存储到 bingdings 中。    // 例如第一次循环就为("__frch_index_0",0)    applyIndex(context, i, uniqueNumber);        // 把 foreach 标签的 item 值里的内容作为 key,本次遍历数组中的值作为 value 存储到 bingdings 中。    // 例如第一次循环就为("userId","1")。注意:由于相同的key会被覆盖住,所以最终存储的为("index",userIds[userIds.length - 1])    // 同时生成一个 key 为 ITEM_PREFIX + item 值内容 + "_" + uniqueNumber,value 为本次遍历数组中的值存储到 bingdings 中。    // 例如第一次循环就为("__frch_userId_0","1")    applyItem(context, o, uniqueNumber);        contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));    if (first) {      first = !((PrefixedContext) context).isPrefixApplied();    }    context = oldContext;    // 计数器加一    i++;  }  // foreach 遍历完,添加 foreach 标签定义的 close 内容  applyClose(context);  return true;}

源码可以知道,问题就出在遍历 dataOrgCodes 这个数组上面。在执行 apply 方法之中有

applyIndex(context, i, uniqueNumber);

applyItem(context, o, uniqueNumber);

#ForEachSqlNodeprivate void applyIndex(DynamicContext context, Object o, int i) {  if (index != null) {    context.bind(index, o);    context.bind(itemizeItem(index, i), o);  }}private void applyItem(DynamicContext context, Object o, int i) {  if (item != null) {    context.bind(item, o);    context.bind(itemizeItem(item, i), o);  }}#DynamicContextpublic void bind(String name, Object value) {bindings.put(name, value);}

从上面的逻辑中可以知道,在遍历 dataOrgCodes 数组的时候,会把我们定义在 foreach 标签中

item、index 属性值作为 key 存储在 DynamicContext 的 bingdings 中,也就是我们传进来的查询参数对象对应的 Map 中,这就导致了虽然我们没有传进来 dataOrgCode 属性,但是在执行 dataOrgCodes 的 foreach 过程中产生了中间值 dataOrgCode,导致最终拼接的 SQL 出现了不该有的条件语句。

Mybatis中SQL节点实例分析

五、解决办法

按道理我们使用的框架是 Mybatis 二次开发的(基本是 Mybatis),应该不会有这么大的问题。所以在发现问题后在本地写了一个 demo 进行复现,发现本地的不会出现这个问题,顿时疑惑了。然后就去了 GitHub 把 Mybatis 的源码拉下来进行比较,最终发现了一些问题。

Mybatis中SQL节点实例分析

Mybatis中SQL节点实例分析

Mybatis 在 2017 年发现了问题并进行了修复,在方法结尾处添加了移除本次 foreach 遍历产生的中间值,也就是从参数映射 Map 中删除了我们定义在 <foreach> 标签的 item、index 定义的 key,这样就不会产生本节的问题。

然而我所用的框架依然是没有更新,用的还是 2012 年版本的代码。所以为了解决这个问题,只能修改 foreach 标签中的 item 的属性值名称,避免和 if 标签的 test 中的属性名称冲突。也就是修改为以下的 SQL 代码。

Mybatis中SQL节点实例分析

感谢各位的阅读,以上就是“Mybatis中SQL节点实例分析”的内容了,经过本文的学习后,相信大家对Mybatis中SQL节点实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: Mybatis中SQL节点实例分析

本文链接: https://lsjlt.com/news/325159.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Mybatis中SQL节点实例分析
    这篇文章主要讲解了“Mybatis中SQL节点实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Mybatis中SQL节点实例分析”吧!一、文章引出原因某天在完成项目中的一个小功能后进行...
    99+
    2023-06-29
  • redis单节点实例分析
    这篇文章主要讲解了“redis单节点实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“redis单节点实例分析”吧!1.安装jdk1.8[root@sht-sgmhadoopdn-04 ...
    99+
    2023-06-03
  • 关于Mybatis中SQL节点的深入解析
    目录一、文章引出原因二、存在的问题三、分析 SQL 生成过程四、分析多余 SQL 的生成五、解决办法六、总结一、文章引出原因 某天在完成项目中的一个小功能后进行自测的时候,发现存在一...
    99+
    2024-04-02
  • Mybatis的SQL注入实例分析
    本文小编为大家详细介绍“Mybatis的SQL注入实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Mybatis的SQL注入实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。前言MyBatis3提供了...
    99+
    2023-06-29
  • JavaScript的节点操作实例分析
    今天小编给大家分享一下JavaScript的节点操作实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一...
    99+
    2024-04-02
  • JQuery中DOM节点的示例分析
    小编给大家分享一下JQuery中DOM节点的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!具体如下:Jquery中DOM...
    99+
    2024-04-02
  • HTML中DOM节点的示例分析
    小编给大家分享一下HTML中DOM节点的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  在HTML DOM中,所有事物...
    99+
    2024-04-02
  • React中元素、组件、实例和节点的示例分析
    这篇文章主要介绍React中元素、组件、实例和节点的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮...
    99+
    2024-04-02
  • PostgreSQL中Old Master节点分析
    本篇内容介绍了“PostgreSQL中Old Master节点分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有...
    99+
    2024-04-02
  • Java字节码实例分析
    这篇文章主要讲解了“Java字节码实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java字节码实例分析”吧!一:Java字节代码的组织形式类文件{OxCAFEBABE,小版本号,大版...
    99+
    2023-06-17
  • C/C++字节序实例分析
    这篇文章主要讲解了“C/C++字节序实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C/C++字节序实例分析”吧!字节序        最近在看...
    99+
    2023-06-29
  • Javascript中DOM、节点和获取元素的示例分析
    这篇文章给大家分享的是有关Javascript中DOM、节点和获取元素的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。DOM文档:DOM中的“D”,当创建一个网页并把它加载到Web浏览器中时,它把编写的网...
    99+
    2023-06-25
  • JavaScript中节流的示例分析
    小编给大家分享一下JavaScript中节流的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!前言我们来聊一聊节流——另一个优化函数的思想。我们还是以移动事...
    99+
    2023-06-06
  • Mybatis-Plus中SQL语句组拼原理的的示例分析
    这篇文章主要为大家展示了“Mybatis-Plus中SQL语句组拼原理的的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Mybatis-Plus中SQL语句组拼原理的的示例分析”这篇文章...
    99+
    2023-06-15
  • Mybatis中缓存的示例分析
    这篇文章主要为大家展示了“Mybatis中缓存的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Mybatis中缓存的示例分析”这篇文章吧。缓存Mybat...
    99+
    2024-04-02
  • mybatis中foreach collection的示例分析
    这篇文章主要介绍mybatis中foreach collection的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!在SQL开发过程中,动态构建In集合条件查询是比较常见的用法,在Mybatis中提供了for...
    99+
    2023-05-31
    mybatis foreach collection
  • Mybatis Plus案例分析
    这篇文章主要介绍了Mybatis Plus案例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Mybatis Plus案例分析文章都会有所收获,下面我们一起来看看吧。1 初识Mybatis-Plus  MyB...
    99+
    2023-06-29
  • java中的char占几个字节实例分析
    java中的char占几个字节实例分析       1:“字节”是byte,“位”是bit ;  2: 1 byte = 8 bit ;  char 在Java中是2个字节。jav...
    99+
    2023-05-31
    java char 字节
  • JavaScript节流与防抖实例分析
    本篇内容主要讲解“JavaScript节流与防抖实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript节流与防抖实例分析”吧!一、js防抖和节流在进行窗口的resize、sc...
    99+
    2023-06-29
  • RAC 节点参数不一致的示例分析
    本篇文章给大家分享的是有关RAC 节点参数不一致的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。在Oracle RAC中,有一些参数是...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作