动态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日

相关推荐

  • 以下是几种构建指数基金组合的常用方法: 一、核心

    以下是几种构建指数基金组合的常用方法:一、核心 - 卫星策略1.核心资产配置●选择宽基指数基金作为核心资产,如沪深300指数基金。沪深300涵盖了沪深两市规模大、流动性好的300只股票,能代表A股市场的整体表现。这部分资产通常占组合的较大比例,比如60% - 70%。其作用是获取

    2024-10-28 22:02:12
    0 0
  • A500投资锦囊,来了

    风险提示:基金有风险,投资须谨慎。本观点仅代表当时观点,今后可能发生改变,仅供参考,不构成投资建议或保证,亦不作为任何法律文件。基金过往业绩并不预示其未来表现,基金管理人管理的其他基金的业绩并不构成基金业绩表现的保证。我国基金运作时间较短,不能反映股市发展

    2024-10-28 22:02:07
    0 0
  • 李稻葵谈理财:假如有10万元,会先买1万元保险,再买8万元基金

    近日,清化大学中国经济思想与实践研究院院长李稻葵做客央视财经频道《对话》栏目,对当前宏观经济运行情况发表最新见解。现场,李稻葵被问假如有10万元,该如何分配。李稻葵表示:“首先有个小前提,就是这10万元钱我5年之内不需要急用。首先,我想至少拿出1万元钱买保险,我

    2024-10-28 22:01:57
    0 0
  • 指数基金的三种配置方法

    1976年6月,先锋领航发行了旗下第一款指数基金产品,甚至其名称都叫做First Index Investment Trust(现已改名为Vanguard 500 Index Fund ETF——标普500ETF先锋领航)。这只产品的“第一”,不仅预示着先锋领航指数基金业务的开端,同时也标志着全球指数基金产品0的突破。投

    2024-10-28 21:23:17
    0 0
  • 基金投资:稳健获利的策略选择

    在当今的金融市场中,基金投资已成为众多投资者的热门选择。然而,如何进行有效的基金投资却是一门需要深入研究的学问。基金投资的风险、流动性和收益性一直是投资者关注的焦点。过高的风险可能导致本金的大幅损失,而缺乏流动性则可能在急需资金时陷入困境,当然,追求高收益

    2024-10-28 21:23:13
    0 0
  • 权益理财三季度跑输主流指数和权益型基金,施罗德交银理财欲推新品布局红利策略丨机警理财日报

    南财理财通课题组 黄桂煊榜单排名来自理财通AI全自动化实时排名,如您对数据有疑问,请在文末联系助理进一步核实。权益类理财三季度涨8.98%,跑输主流指数和权益型基金2024年三季度,A股出现V型反转,上证指数在第二季度末跌破3000点之后持续下行,9月20日最低至2700点,9月底

    2024-10-28 21:23:09
    0 0

发表回复

8206

评论列表(0条)

    暂无评论