zoukankan      html  css  js  c++  java
  • Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别

    XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法会调用生成DynamicSqlSourceRawSqlSource这两个帮助类,本文将对此作下简单的简析

    应用场景

    我们在编写mybatis的sql语句的时候,经常用到的是#{}的字符去替代其中的查询入参,偶尔也会在网上看到${}这样的字符使用。
    经过笔者分析源码得知,其实前者调用的为RawSqlSource帮助类进行生成具体的sql,而后者则是通过DynamicSqlSource帮助类来实现的。

    选用逻辑

    我们还是回到XMLLanguageDriver解析类,不管是xml方式还是注解方式解析sql,都会用到TextSqlNode这个包装类,我们可以作下简单的分析。
    这里就以注解方式的解析sql为例

    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        //此处兼容XML方式的解析,条件以<script>为头结点
        if (script.startsWith("<script>")) { // issue #3
          XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
          return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
        } else {
          //①解析Configuration#variable变量
          script = PropertyParser.parse(script, configuration.getVariables()); // issue #127
          TextSqlNode textSqlNode = new TextSqlNode(script);
          // ②根据TextSqlNode的内部属性isDynamic来进行解析帮助类的分配
          if (textSqlNode.isDynamic()) {
            return new DynamicSqlSource(configuration, textSqlNode);
          } else {
            return new RawSqlSource(configuration, script, parameterType);
          }
        }
      }
    

    PropertyParser#parse() ①

    废话不讲,直接上源码

    public static String parse(String string, Properties variables) {
        VariableTokenHandler handler = new VariableTokenHandler(variables);
        GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
        // 此处专门查找`${}`关键字符,并替换为相应的variable值   
        return parser.parse(string);
      }
    

    附属其中的VariableTokenHandler内部静态类-对指定字符进行处理,此处特指${}

    	private Properties variables;
    
        public VariableTokenHandler(Properties variables) {
          this.variables = variables;
        }
    	
        public String handleToken(String content) {
          //如果variables属性中存在则直接替换,没有则返回原来的内容
          if (variables != null && variables.containsKey(content)) {
            return variables.getProperty(content);
          }
          return "${" + content + "}";
        }
    
    1. VariableTokenHandler的替换原则为查找variables中是否含有${}内的key,没有则返回原来的格式

    2. 此处插一句,这里的variables是如何配置呢,spring方面主要通过创建SqlsessionFactoryBean时设置configurationProperties属性即可;
      也可通过ibatis主配置文件的properties节点进行配置

    TextSqlNode#isDynamic() ②

    我们直接看源码

    public boolean isDynamic() {
        // 只要找到${}这样的字符则直接设置其内部属性isDynamic=true
        DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
        GenericTokenParser parser = createParser(checker);
        parser.parse(text);
        // 返回是否为${}方式解析
        return checker.isDynamic();
      }
    

    TextSqlNode看来是用来校验其中的sql语句是否含有${}字符,有则便用DynamicSqlSource方式解析,反之则用RawSqlSource方式解析

    DynamicSqlSource

    笔者此处针对含${}的SQL语句方式进行简单的分析,而#{}的方式读者可自行研究。也可查阅此篇简单了解>>>Mybatis源码解析-BoundSql

    DynamicSqlSource#getBoundSql()

    我们关注下其是如何组装BoundSql对象的,源码奉上

    public BoundSql getBoundSql(Object parameterObject) {
    	//sql语句解析,优先解析${}字符
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);
        //创建StaticSqlSource,其也会去解析#{}字符。
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        //
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
          boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        return boundSql;
      }
    

    此处我们只关注TextSqlNode#apply()方法,看下其是如何解析${}

    TextSqlNode#apply()

    解析${}字符串

    	public boolean apply(DynamicContext context) {
        GenericTokenParser parser = createParser(new BindingTokenParser(context));
        //将解析得到的sql直接存到context对象中
        context.appendSql(parser.parse(text));
        return true;
      }
      
      private GenericTokenParser createParser(TokenHandler handler) {
        return new GenericTokenParser("${", "}", handler);
      }
    

    附属BindingTokenParser解析静态内部类部分源码

    public String handleToken(String content) {
    	  //此处拿取的相当于为方法调用的入参对象,比如dao接口query(List list),指代的便是list对象
    	  Object parameter = context.getBindings().get("_parameter");
          if (parameter == null) {
            context.getBindings().put("value", null);
          } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
            context.getBindings().put("value", parameter);
          }
          //ognl表达式获取值
          Object value = OgnlCache.getValue(content, context.getBindings());
          return (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
        }
    
    1. 由上面得知,${}主要就是对dao接口的参数的内部属性的直接调用,其不会像#{}那样将入参拼装成?预表达式,而是直接生成表达式执行SQL语句,那么这里就会涉及到sql恶意注入的风险

    2. 此处插一句,ognl指的是Object-Graph Navigation Language,其为强大的表达式语言,使用过Struct2/Freemaker等的就会熟悉其中的语法

    小结

    1. DynamicSqlSource解析含有${}的sql语句,而RawSqlSource解析含有#{}的sql语句

    2. DynamicSqlSource涵盖的操作比RawSqlSource多了一步,便是优先处理${}字符,其本身也会调用去解析#{}字符

    3. ${}语句的解析是最终转换为Statement直接执行,其中的参数赋值是直接赋值,不做字符串引号拼装;而#{}语句的解析是最终转换为PrepareStatement预表达式来进行sql执行,安全性很高
      举个例子:sql查询语句为select * from user where name='admin' and pwd=${pwd}

    • 如果pwd参数传入aaa or 1=1,则上述拼装后的结果为select * from user where name='admin' and pwd=aaa or 1=1。这个表达式会恒为真,直接会绕过验证,风险贼高
    • 如果上述采用#{pwd},则传入aaa or 1=1,则最后的生成语句为select * from user='admin' and pwd='aaa or 1=1'。这个表达式验证通不过,有较高的安全性,防止sql恶意注入
    1. 优推#{}方式操作入参,不建议使用${}方式
  • 相关阅读:
    Google Chrome 自定义协议(PROTOCOL)问题的处理
    C# 6.0/7.0 的新特性
    MySQL 5.7.18 压缩包版配置记录
    nginx.conf文件内容详解
    博客添加动态动漫妹子
    TypeScript 3.8beta版
    微信浏览器H5开发常见的坑
    Babel7知识梳理
    雅虎前端优化35条规则
    webpack
  • 原文地址:https://www.cnblogs.com/question-sky/p/7569405.html
Copyright © 2011-2022 走看看