0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

动态Sql介绍

Android编程精选 来源:Android编程精选 2023-05-31 09:34 次阅读

动态Sql介绍

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

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

Mybatis动态解析里面有2个核心的类SqlNode、SqlSource、ExpressionEvaluator。Mybatis动态Sql使用分为2个部分:动态Sql解析、动态Sql拼接执行。

封装SqlNode

SqlNode是在解析Xml文件的时候对动态Sql进行解析,并存在MappedStatement的sqlSource属性中。对于嵌套动态Sql,mybatis用递归调用来进行解析。这块东西个人觉得还是比较绕,所以这块博主准备事例、源码、执行结果一起讲解。

Sql脚本分类

在Mybatis中Sql脚本分为2种类型:静态Sql和动态Sql。下面我们通过具体的源码来看下2者区分。

静态Sql和动态Sql

静态Sql说白了就没有太任何判断了解的Sql脚本。

//Select是查询的一些属性

//这条查询语句select*fromuserwhereid>#{user.id}就是Mybatis中的静态Sql
//静态Sql就是不太任何条件的Sql语句
select*fromuserwhereid>#{user.id}
//这里有if判断条件,Mybatis把带有判断条件的Sql叫动态Sql。
//动态Sql除了if之外还有foreach、where、trim等。具体自己去mybatis官网看下

ANDname=#{user.name}


SqlNode类结果体系

f201471e-ff21-11ed-90ce-dac502259ad0.png

看mybatis代码很多时候可以看到这种结构。每个SqlNode负责自己那块功能。职责单一。SqlNode的核心方法apply就是通过ExpressionEvaluator来解析OGNL表达式数据的。接下来我们看看Mybatis是如何递归解析动态sql脚本的。

//解析Sql脚本节点
publicSqlSourceparseScriptNode(){
//解析静态和动态脚本,并存在MixedSqlNode里面
//这行代码很关键,后面我们会去分析parseDynamicTags这里就是一层一层递归调用该方法把Sql脚本生成MixedSqlNode对象。
MixedSqlNoderootSqlNode=parseDynamicTags(context);
SqlSourcesqlSource=null;
//是否为动态Sql
if(isDynamic){
//动态Sql则生成DynamicSqlSource
sqlSource=newDynamicSqlSource(configuration,rootSqlNode);
}else{
//否则为静态SqlSource
sqlSource=newRawSqlSource(configuration,rootSqlNode,parameterType);
}
returnsqlSource;
}
//Anhighlightedblock
protectedMixedSqlNodeparseDynamicTags(XNodenode){
//创建个SqlNode,这个列表存了当前Sql脚本节点下的所有的SqlNode信息
Listcontents=newArrayList();
NodeListchildren=node.getNode().getChildNodes();
for(inti=0;i< children.getLength(); i++) { 
      XNode child = node.newXNode(children.item(i));
      //判断子元素或属性中的文本内容 || 子元素文档中的 CDATA 部(不会由解析器解析的文本)
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { 
        String data = child.getStringBody("");
        //解析data
        TextSqlNode textSqlNode = new TextSqlNode(data);
        //判断当前的Sql脚本是否为动态脚本
        if (textSqlNode.isDynamic()) { 
          contents.add(textSqlNode);
          isDynamic = true;
        } else { 
          contents.add(new StaticTextSqlNode(data));
        }
        //如果子元素为代表元素,则需要解析子元素
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {  // issue #628
        //获取元素的名字
        String nodeName = child.getNode().getNodeName();
        //根据元素名获取到元素节点的处理器,Mybatis提供了8中元素处理器,ChooseHandler、IfHandler、OtherwiseHandler
        //TrimHandler、BindHandler、WhereHandler、SetHandler、ForEachHandler。博主会给大家分析下IfHandler
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) { 
          throw new BuilderException("Unknown element <" + nodeName + ">inSQLstatement.");
}
//调用对应的handler进行节点处理,递归调用就在这块
handler.handleNode(child,contents);
isDynamic=true;
}
}
//创建MixedSqlNode
returnnewMixedSqlNode(contents);
}
//下面我们看下IfHandler是如何处理,IfHandler是XMLScriptBuilder的内部类
privateclassIfHandlerimplementsNodeHandler{
publicIfHandler(){
//PreventSyntheticAccess
}
//我们着重分析这个方法
@Override
publicvoidhandleNode(XNodenodeToHandle,ListtargetContents){
//调用parseDynamicTags进行节点解析。这里就是递归,又调用了上面的方法。
MixedSqlNodemixedSqlNode=parseDynamicTags(nodeToHandle);
//获取if对应的表达式
Stringtest=nodeToHandle.getStringAttribute("test");
//创建IfSqlNode
IfSqlNodeifSqlNode=newIfSqlNode(mixedSqlNode,test);
targetContents.add(ifSqlNode);
}
}

下面我们根据Sql脚本和执行结果来分析。

//静态Sql脚本和嵌套的动态Sql脚本

select*fromuserwhereid>#{user.id}

ANDname=#{user.name}

ANDname=#{user.name}

ANDname=#{user.name}




下面我们分析下执行结果:

f214e24c-ff21-11ed-90ce-dac502259ad0.png

上面递归结果已经用不通颜色标记了,大家自己看下。特别需要看下IfSqlNode的属性。

动态Sql解析

动态Sql解析主要是执行数据库操作的时候把动态Sql转换成JDBC能识别的Sql脚本。Mybatis中主要是通过SqlSource来解析Sql脚本,替换成JDBC能识别的Sql脚本。我们先看下类图。

f25b2838-ff21-11ed-90ce-dac502259ad0.png

SqlSource:提供了Sql解析的行为。
RawSqlSource:静态Sql脚本的编译,只生成一次StaticSqlSource。
DynamicSqlSource:每次调用都会生成StaticSqlSource。每次调用传入参数可能不一样。需要每次生成StaticSqlSource。
ProviderSqlSource:第三方脚本语言的集成。
FreeMarkerSqlSource:对FreeMarker的支持。
StaticSqlSource:StaticSqlSource只是对上面4中类型做了层封装。博主没有这个类会更清爽些。
我们这次主要对StaticSqlSource、RawSqlSource、和DynamicSqlSource进行分析。

StaticSqlSource

其实StaticSqlSource就是对其他几种类型Sql处理器结果进行包装。我们看下源码。

//我们主要分析下getBoundSql
publicclassStaticSqlSourceimplementsSqlSource{

privatefinalStringsql;
privatefinalListparameterMappings;
privatefinalConfigurationconfiguration;

publicStaticSqlSource(Configurationconfiguration,Stringsql){
this(configuration,sql,null);
}

publicStaticSqlSource(Configurationconfiguration,Stringsql,ListparameterMappings){
this.sql=sql;
this.parameterMappings=parameterMappings;
this.configuration=configuration;
}

//getBoundSql就是创建一个BoundSql对象。
@Override
publicBoundSqlgetBoundSql(ObjectparameterObject){
returnnewBoundSql(configuration,sql,parameterMappings,parameterObject);
}

}

看完是不是非常简单,其实有些代码确实没有我们想象中那么难。

RawSqlSource

//我们着重分析RawSqlSource方法
publicclassRawSqlSourceimplementsSqlSource{

privatefinalSqlSourcesqlSource;

publicRawSqlSource(Configurationconfiguration,SqlNoderootSqlNode,ClassparameterType){
this(configuration,getSql(configuration,rootSqlNode),parameterType);
}
//这里实现了对静态脚本的解析,所谓的静态脚本解析就是把#{}解析成?静态Sql解析是在解析Mapper.xml的时候执行的
publicRawSqlSource(Configurationconfiguration,Stringsql,ClassparameterType){
SqlSourceBuildersqlSourceParser=newSqlSourceBuilder(configuration);
Classclazz=parameterType==null?Object.class:parameterType;
//通过调用SqlSourceBuilder的parse方法来解析Sql
sqlSource=sqlSourceParser.parse(sql,clazz,newHashMap());
}

privatestaticStringgetSql(Configurationconfiguration,SqlNoderootSqlNode){
DynamicContextcontext=newDynamicContext(configuration,null);
rootSqlNode.apply(context);
returncontext.getSql();
}

@Override
publicBoundSqlgetBoundSql(ObjectparameterObject){
returnsqlSource.getBoundSql(parameterObject);
}

}

下面我们来看下SqlSourceBuilder的parse方法

publicSqlSourceparse(StringoriginalSql,ClassparameterType,MapadditionalParameters){
ParameterMappingTokenHandlerhandler=newParameterMappingTokenHandler(configuration,parameterType,additionalParameters);
//找到Sql脚本中#{}符号的脚本用?号进行替代。GenericTokenParser里面代码比较复杂,博主也没有研究。
//有兴趣自己可以研究下。
GenericTokenParserparser=newGenericTokenParser("#{","}",handler);
Stringsql=parser.parse(originalSql);
returnnewStaticSqlSource(configuration,sql,handler.getParameterMappings());
}

DynamicSqlSource

动态Sql解析主要由DynamicSqlSource来完成。这里面又是通过递归调进行sql解析。我们还是延用上面的Sql给大家讲解。

publicclassDynamicSqlSourceimplementsSqlSource{

privatefinalConfigurationconfiguration;
privatefinalSqlNoderootSqlNode;

publicDynamicSqlSource(Configurationconfiguration,SqlNoderootSqlNode){
this.configuration=configuration;
this.rootSqlNode=rootSqlNode;
}

@Override
publicBoundSqlgetBoundSql(ObjectparameterObject){
//动态Sql解析上下文
DynamicContextcontext=newDynamicContext(configuration,parameterObject);
//rootSqlNode就是我们前面讲解的,把动态Sql解析成SqlNode对象。外层为MixedSqlNode节点,节点存储了
//节点下的所有子节点。里面递归调用并根据传入参数的属性检查是否需要拼接sql
rootSqlNode.apply(context);
//这块代码和上面静态Sql接代码一致。
SqlSourceBuildersqlSourceParser=newSqlSourceBuilder(configuration);
ClassparameterType=parameterObject==null?Object.class:parameterObject.getClass();
//把我们动态Sql中的#{}替换成?
SqlSourcesqlSource=sqlSourceParser.parse(context.getSql(),parameterType,context.getBindings());
BoundSqlboundSql=sqlSource.getBoundSql(parameterObject);
for(Map.Entryentry:context.getBindings().entrySet()){
boundSql.setAdditionalParameter(entry.getKey(),entry.getValue());
}
returnboundSql;
}

}

动态Sql解析apply方法博主只根据场景介绍下MixedSqlNode和IfSqlNode的apply方法。其他有兴趣自己去研究下。逻辑大体一致,实现有些区别。

publicclassMixedSqlNodeimplementsSqlNode{
privatefinalListcontents;

publicMixedSqlNode(Listcontents){
this.contents=contents;
}

//获取循环SqlNode列表的所有SqlNode,调用apply方法根据传入参数和条件进行静态sql的拼接。
//列表中的SqlNode可能是一个简单的SqlNode对象,也可能是一个MixedSqlNode或者有更多的嵌套。
//博主的例子就是3个嵌套If查询。根据博主的Sql脚本,这里直接会调用IfSqlNode的apply方法。
//我们接下来看下IfSqlNode是如何实现的。
@Override
publicbooleanapply(DynamicContextcontext){
for(SqlNodesqlNode:contents){
sqlNode.apply(context);
}
returntrue;
}
}

IfSqlNode的apply

publicclassIfSqlNodeimplementsSqlNode{
//ExpressionEvaluator会调用ognl来对表达式进行解析
privatefinalExpressionEvaluatorevaluator;
privatefinalStringtest;
privatefinalSqlNodecontents;

publicIfSqlNode(SqlNodecontents,Stringtest){
this.test=test;
this.contents=contents;
this.evaluator=newExpressionEvaluator();
}

@Override
publicbooleanapply(DynamicContextcontext){
//context.getBindings()里面就存储这请求参数,这里是一个HashMap,OGNl里面代码博主没有研究。
//如果条件if成立,直接获取contents中的SqlNode的apply方法进行动态脚本处理。
if(evaluator.evaluateBoolean(test,context.getBindings())){
contents.apply(context);
returntrue;
}
returnfalse;
}

}

这块代码很多递归调用,博主自认为讲的不太透彻,所以大家看完务必自己去调试下。

总结

Mybatis动态Sql从解析到执行分为2个过程下面对这个2个过程进行简单总结。
1.动态Sql生成SqlNode信息,这个过程发生在对select、update等Sql语句解析过程。如果是静态Sql直接会把#{}替换成?。
2.动态Sql解析在获取BoundSql时候触发。会调用SqlNode的apply进行Sql解析成静态Sql,然后把#{}替换成?,并绑定ParameterMapping映射。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • SQL
    SQL
    +关注

    关注

    1

    文章

    759

    浏览量

    44060
  • 源码
    +关注

    关注

    8

    文章

    633

    浏览量

    29134
  • 脚本
    +关注

    关注

    1

    文章

    387

    浏览量

    14829

原文标题:Mybatis动态Sql处理

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    在Delphi中动态地使用SQL查询语句

    在Delphi中动态地使用SQL查询语句在一般的数据库管理系统中,通常都需要应用SQL查询语句来提高程序的动态特性。下面介绍如何在Delph
    发表于 05-10 11:10

    DRDS分布式SQL引擎—执行计划介绍

    摘要: 本文着重介绍 DRDS 执行计划中各个操作符的含义,以便用户通过查询计划了解 SQL 执行流程,从而有针对性的调优 SQL。DRDS分布式SQL引擎 — 执行计划
    发表于 07-12 17:01

    手写SQL编译器——文法介绍

    精读《手写 SQL 编译器 - 文法介绍
    发表于 05-29 13:35

    为什么要动态sql语句?

    为什么要动态sql语句?因为动态sql语句能够提供一些比较友好的机制1、可以使得一些在编译过程中无法获得完整的sql语句,在程序执行阶段
    发表于 12-20 06:00

    基于数据窗口对象SQL 语法的动态移植技术探讨

    在PowerBuilder9.0 应用程序中,动态创建特定显示风格和结构比较复杂的数据窗口对象是开发人员所面临的一大难题。文章就该问题提出通过移植数据窗口对象SQL 语法的方法来实现
    发表于 05-30 10:29 11次下载

    Delphi教程之在SQL查询中使用动态参数

    Delphi教程之在SQL查询中使用动态参数,学习Delphi的必备资料。
    发表于 03-31 11:29 4次下载

    SQL相关知识解析及SQL完全手册的免费分享

    本文介绍SQL的基础知识、SQL快速入门及SQL编程手册的分享。
    发表于 11-22 11:31 0次下载
    <b class='flag-5'>SQL</b>相关知识解析及<b class='flag-5'>SQL</b>完全手册的免费分享

    mybatis动态sql详解

    本文详细介绍了mybatis执行动态sql语句的方法。
    发表于 02-24 11:37 3819次阅读

    SQL教程之什么是SQL能做什么SQL基础的详细资料介绍

    SQL 是一门 ANSI 的标准计算机语言,用来访问和操作数据库系统.SQL 语句用于取回和更新数据库中的数据.SQL 可与数据库程序协同工作,比如 MS Access、DB2、Informix、MS
    发表于 12-10 08:00 8次下载

    java的动态SQL详细资料说明

    首先,所谓SQL动态和静态,是指SQL语句在何时被编译和执行,二者都是用在SQL嵌入式编程中的,这里所说的嵌入式是指将SQL语句嵌入在高级
    发表于 06-06 17:51 0次下载
    java的<b class='flag-5'>动态</b><b class='flag-5'>SQL</b>详细资料说明

    SQL注入到Getshell的教程

    上一节,我们已经介绍了基本的SQL查询语句,常见的SQL注入类型,DVWA靶场演示SQL注入。学习了上一节我们可以做到执行任意SQL语句,主
    的头像 发表于 09-21 14:45 2957次阅读

    一文掌握MyBatis的动态SQL使用与原理

    摘要:使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的
    的头像 发表于 01-06 11:27 943次阅读

    PROC SQL介绍

    SQL(Structured Query Language)——结构化查询语言,是用于检索和更新数据的一种标准化语言,SQL在SAS中通过PROC SQL来实现。
    的头像 发表于 05-19 16:10 2613次阅读
    PROC <b class='flag-5'>SQL</b><b class='flag-5'>介绍</b>

    MyBatis动态sql是什么?MyBatis动态SQL最全教程

    动态 SQL 是 MyBatis 的强大特性之一。在 JDBC 或其它类似的框架中,开发人员通常需要手动拼接 SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。
    的头像 发表于 08-10 10:18 916次阅读

    oracle sql 定义变量并赋值

    在Oracle SQL中,变量是用来存储数据值的标识符。通过定义和使用变量,我们可以在SQL语句中使用它们来存储和处理数据,从而实现更灵活和动态的查询和操作。 在Oracle SQL
    的头像 发表于 12-06 10:46 2662次阅读