动态SQL实现原理六-从源码角度分析#{}和${}的区别

前面几节介绍了MyBatis动态SQL相关的一些概念,并分析了MyBatis动态SQL配置的解析过程,本节我们就从源码的角度分析一下MyBatis两种常用的参数占位符#{}和${}的区别。

我们首先来看${}参数占位符的解析过程。当动态SQL配置中存在${}参数占位符时,MyBatis会使用TextSqlNode对象描述对应的SQL节点,在调用TextSqlNode对象的apply()方法时会完成动态SQL的解析。也就是说,${}参数占位符的解析是在TextSqlNode类的apply()方法中完成的,下面是该方法的实现:

动态SQL实现原理六-从源码角度分析#{}和${}的区别

public boolean apply(DynamicContext context) {    // 通过GenericTokenParser对象解析${}参数占位符,使用BindingTokenParser对象处理参数占位符内容    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));    // 调用GenericTokenParser对象的parse()方法解析    context.appendSql(parser.parse(text));    return true;  }  private GenericTokenParser createParser(TokenHandler handler) {    return new GenericTokenParser("${", "}", handler);  }

如上面的代码所示,在TextSqlNode类的apply()方法中,首先调用createParser()方法创建一个GenericTokenParser对象,通过GenericTokenParser对象解析${}参数占位符,然后通过BindingTokenParser对象处理参数占位符的内容。

createParser方法返回一个GenericTokenParser对象,指定openToken属性为“${”,closeToken属性为“}”,TokenHandler为BindingTokenParser对象。GenericTokenParser解析参数占位符的过程前面已经介绍过了,这里我们回顾一下:

 public String parse(String text) {    if (text == null || text.isEmpty()) {      return "";    }    // 获取第一个openToken在SQL中的位置    int start = text.indexOf(openToken, 0);    // start为-1说明SQL中不存在任何参数占位符    if (start == -1) {      return text;    }    // 將SQL转换为char数组    char[] src = text.toCharArray();    // offset用于记录已解析的#{或者}的偏移量,避免重复解析    int offset = 0;    final StringBuilder builder = new StringBuilder();    // expression为参数占位符中的内容    StringBuilder expression = null;    // 遍历获取所有参数占位符的内容,然后调用TokenHandler的handleToken()方法替换参数占位符    while (start > -1) {      if (start > 0 && src[start - 1] == '\\') {        // this open token is escaped. remove the backslash and continue.        builder.append(src, offset, start - offset - 1).append(openToken);        offset = start + openToken.length();      } else {        // found open token. let's search close token.        if (expression == null) {          expression = new StringBuilder();        } else {          expression.setLength(0);        }        builder.append(src, offset, start - offset);        offset = start + openToken.length();        int end = text.indexOf(closeToken, offset);        while (end > -1) {          if (end > offset && src[end - 1] == '\\') {            // this close token is escaped. remove the backslash and continue.            expression.append(src, offset, end - offset - 1).append(closeToken);            offset = end + closeToken.length();            end = text.indexOf(closeToken, offset);          } else {            expression.append(src, offset, end - offset);            offset = end + closeToken.length();            break;          }        }        if (end == -1) {          // close token was not found.          builder.append(src, start, src.length - start);          offset = src.length;        } else {          // 调用TokenHandler的handleToken()方法替换参数占位符          builder.append(handler.handleToken(expression.toString()));          offset = end + closeToken.length();        }      }      start = text.indexOf(openToken, offset);    }    if (offset < src.length) {      builder.append(src, offset, src.length - offset);    }    return builder.toString();  }

上面代码的核心内容是遍历获取所有${}参数占位符的内容,然后调用BindingTokenParser对象的handleToken()方法对参数占位符内容进行替换。BindingTokenParser类的handleToken()方法实现如下:

  public String handleToken(String content) {      // 获取Mybatis内置参数_parameter,_parameter属性中保存所有参数信息      Object parameter = context.getBindings().get("_parameter");      if (parameter == null) {        context.getBindings().put("value", null);      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {        // 將参数对象添加到ContextMap对象中        context.getBindings().put("value", parameter);      }      // 通过OGNL表达式获取参数值      Object value = OgnlCache.getValue(content, context.getBindings());      String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"      checkInjection(srtValue);      // 返回参数值      return srtValue;    }

如上面的代码所示,在BindingTokenParser类的handleToken()方法中,根据参数占位符名称获取对应的参数值,然后替换为对应的参数值。假设我们的SQL配置如下:

    <select id="getUserByName" parameterType="java.lang.String"            resultType="com.blog4java.mybatis.example.entity.UserEntity">      select * from user where name = ${userName}    </select>

如果Mapper调用时传入的参数值如下:

    @Test    public void testGetUserByName() {             //   String userName = "'User6'";// 正常        String userName = "User6";// 错误,类型不匹配        UserEntity userEntity = userMapper.getUserByName(userName);        System.out.println(userEntity);    }

上面的Mapper调用将会抛出异常,原因是TextSqlNode类的apply()方法中解析${}参数占位符时,只是对参数占位符内容进行替换,将参数占位符替换为对应的参数值,因此SQL配置解析后的内容如下:

select * from user where name =User6

#{}参数占位符的解析过程前面已经介绍过了,可参考SqlSourceBuilder类的parse()方法。假设我们有如下SQL配置:

    <select id="getUserByName" parameterType="java.lang.String"            resultType="com.blog4java.mybatis.example.entity.UserEntity">      select * from user where name = #{userName}    </select>

#{}参数占位符的内容将会被替换为“?”。上面的SQL语句解析后的结果如下:

select * from user where name =?

MyBatis将会使用PreparedStatement对象与数据库进行交互,过程大致如下:

Connection connection = DriverManager.getConnection("xxx");PreparedStatement statement = connection.prepareStatement("select * from userwhere name = ?");statement.setString(1,"Test4");statement.execute();

最后我们再来总结一下#{}和${}参数占位符的区别。使用#{}参数占位符时,占位符内容会被替换成“?”,然后通过PreparedStatement对象的setXXX()方法为参数占位符设置值;而${}参数占位符内容会被直接替换为参数值。使用#{}参数占位符能够有效避免SQL注入问题,所以我们可以优先考虑使用#{}占位符,当#{}参数占位符无法满足需求时,才考虑使用${}参数占位符。

版权声明

1 本文地址:https://www.sunlonger.com/jijin/1230237.html 转载请注明出处。
2 本站内容除隼龙儿财经签约编辑原创以外,部分来源网络由互联网用户自发投稿及AIGC生成仅供学习参考。
3 文章观点仅代表原作者本人不代表本站立场,并不完全代表本站赞同其观点和对其真实性负责。
4 文章版权归原作者所有,部分转载文章仅为传播更多信息服务用户,如信息标记有误请联系管理员。
5 本站禁止以任何方式发布转载违法违规相关信息,如发现本站有涉嫌侵权/违规及任何不妥内容,请第一时间联系我们申诉反馈,经核实立即修正或删除。


本站仅提供信息存储空间服务,部分内容不拥有所有权,不承担相关法律责任。
上一篇 2024年06月17日
下一篇 2024年06月17日

相关推荐

  • 新手选好基金的关键与投资技巧 |基金宝典

    对于新手来说,选择基金是一个需要谨慎考虑的过程,首先,要明确自己的投资目标,是追求短期收益还是长期增值,或者是为了特定的财务目标(如子女教育、退休养老等)。同时,评估自己对投资风险的承受能力,风险承受能力较低的投资者可以选择低风险的货币市场基金或债券型基金

    2024-09-14 21:32:10
    0 0
  • 基金基础篇:一文讲透指数基金,干货满满

    投资千万条,风险第一条。前言:对于基金投资而言,绕不开的一个话题就是指数基金,那么到底什么是指数基金呢?它有哪些魅力呢?指数基金的优缺点是啥呢?等一系列问题,本篇就来展开详解,一起往下!对于基金老手而言,可以选择性阅读!01一、什么是指数?在说指数基金之前,

    2024-09-14 21:32:07
    0 0
  • 基金在什么平台上买比较合理?

    购买基金的平台,各有特点和优势,你可以根据自己的需求进行选择,以下是一些基金购买平台:1. 基金公司官方网站或APP:- 优点:直接从基金公司购买,能获取最准确的产品信息和相关服务,有时还可能享受费率优惠,比如申购费折扣等;对自家基金产品的研究和理解更深入,能提供

    2024-09-14 21:32:02
    0 0
  • 购买货币基金有哪些技巧?

    来源:龙老解股以下是购买货币基金的一些技巧: **一、选择规模适中的基金** 一般来说,规模适中的货币基金在市场上的议价能力较强,同时也能在操作上保持灵活性。如果规模过小,可能在应对大额赎回时面临流动性风险,并且在与银行等机构谈判获取更高利率时处于劣势;而规模过

    2024-09-14 21:31:59
    0 0
  • 【理财狮talk秀】债市也波动?别急,先看看这五要素

    据wind数据显示,今年开年以来债券市场吸引了大量资金的关注,走出了非常亮眼的行情,不过也引发了市场和监管对于持续上涨背后潜在风险的担忧。自8月5日以来,债券市场出现了明显的波动,债券价格持续下跌。走势相对较稳的债券为什么出现了较大的波动呢?今天,就让我们来聊一

    2024-09-14 21:31:58
    0 0
  • 几种止损的方式

    XX股票跌了好多,需要止损吗?关于这个问题,相信炒股的朋友都遇到过。买入被套后,不少人会选择原地躺下,等待股价回归。但事实上,随着A股越来越成熟,国际化进程越来越快,以往很多老股民的这个解套“经验”可能不那么灵了。运气不好的,甚至可能被套到退市!众所周知,投

    2024-09-14 21:31:56
    0 0

发表回复

8206

评论列表(0条)

    暂无评论