动态Sql介绍

动态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 * from user where id > #{user.id}就是mybatis中的静态sql       //静态sql就是不太任何条件的sql语句       select * from user where id > #{ user.id}       //这里有if判断条件,mybatis把带有判断条件的sql叫动态sql。       //动态sql除了if之外还有foreach、where、trim等。具体自己去mybatis官网看下                    and name = #{ user.name}             
sqlnode类结果体系
看mybatis代码很多时候可以看到这种结构。每个sqlnode负责自己那块功能。职责单一。sqlnode的核心方法apply就是通过expressionevaluator来解析ognl表达式数据的。接下来我们看看mybatis是如何递归解析动态sql脚本的。
// 解析sql脚本节点  public sqlsource parsescriptnode() {     //解析静态和动态脚本,并存在mixedsqlnode里面    //这行代码很关键,后面我们会去分析parsedynamictags这里就是一层一层递归调用该方法把sql脚本生成mixedsqlnode对象。    mixedsqlnode rootsqlnode = parsedynamictags(context);    sqlsource sqlsource = null;    //是否为动态sql    if (isdynamic) {       //动态sql则生成dynamicsqlsource      sqlsource = new dynamicsqlsource(configuration, rootsqlnode);    } else {       //否则为静态sqlsource      sqlsource = new rawsqlsource(configuration, rootsqlnode, parametertype);    }    return sqlsource;  }// an highlighted blockprotected mixedsqlnode parsedynamictags(xnode node) {     //创建个sqlnode,这个列表存了当前sql脚本节点下的所有的sqlnode信息    list contents = new arraylist();    nodelist children = node.getnode().getchildnodes();    for (int i = 0; i  #{ user.id}                    and name = #{ user.name}                            and name = #{ user.name}                                    and name = #{ user.name}                                         
下面我们分析下执行结果:
上面递归结果已经用不通颜色标记了,大家自己看下。特别需要看下ifsqlnode的属性。
动态sql解析
动态sql解析主要是执行数据库操作的时候把动态sql转换成jdbc能识别的sql脚本。mybatis中主要是通过sqlsource来解析sql脚本,替换成jdbc能识别的sql脚本。我们先看下类图。
sqlsource:提供了sql解析的行为。
rawsqlsource:静态sql脚本的编译,只生成一次staticsqlsource。
dynamicsqlsource:每次调用都会生成staticsqlsource。每次调用传入参数可能不一样。需要每次生成staticsqlsource。
providersqlsource:第三方脚本语言的集成。
freemarkersqlsource:对freemarker的支持。
staticsqlsource:staticsqlsource只是对上面4中类型做了层封装。博主没有这个类会更清爽些。
我们这次主要对staticsqlsource、rawsqlsource、和dynamicsqlsource进行分析。
staticsqlsource
其实staticsqlsource就是对其他几种类型sql处理器结果进行包装。我们看下源码。
//我们主要分析下getboundsqlpublic class staticsqlsource implements sqlsource {   private final string sql;  private final list parametermappings;  private final configuration configuration;  public staticsqlsource(configuration configuration, string sql) {     this(configuration, sql, null);  }  public staticsqlsource(configuration configuration, string sql, list parametermappings) {     this.sql = sql;    this.parametermappings = parametermappings;    this.configuration = configuration;  }  //getboundsql就是创建一个boundsql对象。  @override  public boundsql getboundsql(object parameterobject) {     return new boundsql(configuration, sql, parametermappings, parameterobject);  }}  
看完是不是非常简单,其实有些代码确实没有我们想象中那么难。
rawsqlsource
// 我们着重分析rawsqlsource方法public class rawsqlsource implements sqlsource {   private final sqlsource sqlsource;  public rawsqlsource(configuration configuration, sqlnode rootsqlnode, class parametertype) {     this(configuration, getsql(configuration, rootsqlnode), parametertype);  }  //这里实现了对静态脚本的解析,所谓的静态脚本解析就是把 #{}解析成?静态sql解析是在解析mapper.xml的时候执行的  public rawsqlsource(configuration configuration, string sql, class parametertype) {     sqlsourcebuilder sqlsourceparser = new sqlsourcebuilder(configuration);    class clazz = parametertype == null ? object.class : parametertype;    //通过调用sqlsourcebuilder的parse方法来解析sql    sqlsource = sqlsourceparser.parse(sql, clazz, new hashmap());  }  private static string getsql(configuration configuration, sqlnode rootsqlnode) {     dynamiccontext context = new dynamiccontext(configuration, null);    rootsqlnode.apply(context);    return context.getsql();  }  @override  public boundsql getboundsql(object parameterobject) {     return sqlsource.getboundsql(parameterobject);  }}  
下面我们来看下sqlsourcebuilder的parse方法
public sqlsource parse(string originalsql, class parametertype, map additionalparameters) {     parametermappingtokenhandler handler = new parametermappingtokenhandler(configuration, parametertype, additionalparameters);    //找到sql脚本中#{}符号的脚本用?号进行替代。generictokenparser里面代码比较复杂,博主也没有研究。    //有兴趣自己可以研究下。    generictokenparser parser = new generictokenparser(#{, }, handler);    string sql = parser.parse(originalsql);    return new staticsqlsource(configuration, sql, handler.getparametermappings());  }  
dynamicsqlsource
动态sql解析主要由dynamicsqlsource来完成。这里面又是通过递归调进行sql解析。我们还是延用上面的sql给大家讲解。
public class dynamicsqlsource implements sqlsource {   private final configuration configuration;  private final sqlnode rootsqlnode;  public dynamicsqlsource(configuration configuration, sqlnode rootsqlnode) {     this.configuration = configuration;    this.rootsqlnode = rootsqlnode;  }  @override  public boundsql getboundsql(object parameterobject) {     //动态sql解析上下文    dynamiccontext context = new dynamiccontext(configuration, parameterobject);    //rootsqlnode就是我们前面讲解的,把动态sql解析成sqlnode对象。外层为mixedsqlnode节点,节点存储了    //节点下的所有子节点。里面递归调用并根据传入参数的属性检查是否需要拼接sql    rootsqlnode.apply(context);    //这块代码和上面静态sql接代码一致。    sqlsourcebuilder sqlsourceparser = new sqlsourcebuilder(configuration);    class parametertype = parameterobject == null ? object.class : parameterobject.getclass();    //把我们动态sql中的#{}替换成?    sqlsource sqlsource = sqlsourceparser.parse(context.getsql(), parametertype, context.getbindings());    boundsql boundsql = sqlsource.getboundsql(parameterobject);    for (map.entry entry : context.getbindings().entryset()) {       boundsql.setadditionalparameter(entry.getkey(), entry.getvalue());    }    return boundsql;  }}  
动态sql解析apply方法博主只根据场景介绍下mixedsqlnode和ifsqlnode的apply方法。其他有兴趣自己去研究下。逻辑大体一致,实现有些区别。
public class mixedsqlnode implements sqlnode {   private final list contents;  public mixedsqlnode(list contents) {     this.contents = contents;  } //获取循环sqlnode列表的所有sqlnode,调用apply方法根据传入参数和条件进行静态sql的拼接。 //列表中的sqlnode可能是一个简单的sqlnode对象,也可能是一个mixedsqlnode或者有更多的嵌套。 //博主的例子就是3个嵌套if查询。根据博主的sql脚本,这里直接会调用ifsqlnode的apply方法。 //我们接下来看下ifsqlnode是如何实现的。  @override  public boolean apply(dynamiccontext context) {     for (sqlnode sqlnode : contents) {       sqlnode.apply(context);    }    return true;  }}  
ifsqlnode的apply
public class ifsqlnode implements sqlnode {   //expressionevaluator会调用ognl来对表达式进行解析   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();  }  @override  public boolean apply(dynamiccontext context) {     //context.getbindings()里面就存储这请求参数,这里是一个hashmap,ognl里面代码博主没有研究。    //如果条件if成立,直接获取contents中的sqlnode的apply方法进行动态脚本处理。    if (evaluator.evaluateboolean(test, context.getbindings())) {       contents.apply(context);      return true;    }    return false;  }}  
这块代码很多递归调用,博主自认为讲的不太透彻,所以大家看完务必自己去调试下。
总结
mybatis动态sql从解析到执行分为2个过程下面对这个2个过程进行简单总结。
1.动态sql生成sqlnode信息,这个过程发生在对select、update等sql语句解析过程。如果是静态sql直接会把#{}替换成?。
2.动态sql解析在获取boundsql时候触发。会调用sqlnode的apply进行sql解析成静态sql,然后把#{}替换成?,并绑定parametermapping映射。


iPhone 11降价销售继续抢市场提高出货量
郑州易度加速度传感器
电网侧储能规模将超万亿
小米6处理器曝光:应该就是骁龙835了,你看人家都公开打情骂俏了!
学习嵌入式系统的10点建议
动态Sql介绍
拆焊Flash芯片的方法及步骤教程
提高PLC运行速度的编程方法
迷你台式电源的制作
海信移动空调新品的首次亮相 掀起行业舒适新风潮
显卡行业现状:独立显卡与集成显卡多维度对比及应用场景
可穿戴式装置亟需专用SoC
多个加密货币交易平台成为DDoS战场
华为旗舰机1/1.7英寸CMOS,依然力压很多手机
铝制散热片及内存散热马甲作用详解
tuple函数怎么返回多个值
光纤行业领头羊长飞光纤再传喜报,成为国内光纤行业首家A+H股两地上市的企业
iQOO和小米9哪个最值得入手
薄膜在线检测系统可快速检测出产品的瑕疵
OPPO新专利曝光在手机侧面安装了弹出式摄像头