从源码的角度回答“mybatis的#{} 和${}有什么区别”?

java程序员面试的时候,碰上面试官问:mybatis 的#{ }和 ${} 有什么区别? 大概率你会这样回答:

1、#{}是预编译处理,${}是字符串替换.

2、MyBatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值而在处理 时,是把 {}时,是把 时,是把{}替换为变量的值。

3.使用#{}可以有效地防止SQL注入,提高系统安全性;因为一个#{}被解析为一个参数占位符,而${}仅仅为一个纯粹的String替换。

对应问题的答案,你能说出三个区别,而且在实际的开发中,你也会发现,在很多时候我们都是用#{},用${} 的机会相对比较少.

但是你了解在mybatis 源代码是如何对#{}和 ${} 进行处理的吗?

在了解#{ } 和${} 区别之前,我们需要先来了解一下动态SQL以及动态SQL的解析过程,动态SQL 指的是事先无法预知具体的条件,需要运行时根据具体的情况动态生成SQL语句。

<select id="findUserByConditon" resultMap="userMap">
     select * from t_user 
    <where>
      <if test="id !=null">
        AND  id =#{id}
      </if>
      <if test="username !=null">
        AND  user_name =#{username}
      </if>
    </where>
  </select>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UPyS1iyz-1656037659788)(<> “点击并拖拽以移动”)]

若想了解mybatis 的动态SQL 的解析过程,需要从XMLLanguageDriver类的createSqlSource()方法出发进行解析,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-466pXmLs-1656037659794)(<> “点击并拖拽以移动”)]​

从源代码中得知Mapper SQL 配置的解析是委托给XMLScriptBuilder类来完成,XMLScriptBuilder.parseScriptNode()方法完成解析工作,其源码如下:

image.png
​从源码中得知,在判断是否为动态SQL,如果为动态SQL ,则创建DynamicSqlSource对象。mybatis判断首付为动态sql的依据是SQL配置是否包含、、 等元素或者${}参数占位符。判断是否因为动态Sql的源代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKEJqhfJ-1656037659798)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a26818cc771648258485668526c3e420~tplv-k3u1fbpfcp-watermark.image?)]

在此方法中对SQL配置的所有子元素进行遍历,如果子元素类型为SQL文本,则使用TextSqlNode对象描述SQL节点信息,若SQL节点中存在${} 参数占位符,则是设置为动态SQL, 如果子元素未、、等标签,则使用NodeHandler处理,并设置为动态SQL;设置为动态SQL方式是定义boolean类型的全局变量isDynamic,为isDynamic=true则为动态SQL。

当动态SQL标签解析完成后,将解析后生成的SqlNode对象封装在SqlSource对象中,SqlSource创建完成后,最终会存放在MappedStatement对象的sqlSource属性中,Executor 组件操作数据库时,MappedStatement 对象的getBoundSql()方法会调用SQLSource对象getBoundSql()方法 完成sqlNode对象解析成SQL语句的过程。 DynamicSqlSource.getBoundSql()方法的源码如下:

image.png

在方法中,调用了SqlNode对象的apply()方法对动态SQL进行解析。在方法中创建SqlSourceBuilder对象,并调用SqlSourceBuilder.parse()方法对动态SQL进行解析,解析的过程如下:

image.png

在方法中共创建了一个ParameterMappingTokenHandler对象用于处理SQL中的#{}参数占位符。接着在创建一个GenericTokenParser对象对SQL中的#{}参数占位符进行解析,获取#{}参数占位符中的内容。GenericTokenParser对象对SQL中的#{}参数占位符的解析源码如下:

image.png

在方法中,调用了ParameterMappingTokenHandler.handleToken()对参数占位符内容进行替换;

image.png

从源码可以看出SQL 配置中的所有#{} 参数占位符内容都被替换成了“?”字符。替换的原因是因为mybatis 默认情况下会使用PreparedStatement对象与数据库交互,然后调用PreparedStatement对象的setXXX()方法为参数占位符设置值。在此方法中,还调用了buildParameterMapping()方法对占位符内容进行解析,将占位符内容转换为ParameterMapping对象,便于后续根据参数映射信息获取对应的TypeHandler为PreparedStatement对象设置值。在buildParameterMapping()方法中会将参数占位符内容转换为Map对象。

image.png
到此为止,动态SQL 的解析就完成了。在解析动态sql的时候,对#{}进行了相关的解析处理。接下来我们再来看看${}的源码解析。

当动态sql配置中存在 参数占位符时, m y b a t i s 会调用 T e x t S q l N o d e 类的 a p p l y ( ) 方法中完成 {}参数占位符时,mybatis会调用TextSqlNode类的apply()方法中完成 参数占位符时,mybatis会调用TextSqlNode类的apply()方法中完成{}参数占位符的解析工作。

image.png

在GenericTokenParser()(在前文已介绍过) 主要是遍历获取所有${} 参数占位符的内容,然后调用BindingTokenParser对象的handleToken()方法对参数占位符内容进行替换。

image.png

总结:

使用#{}参数占位符时,占位符内容会被替换成“?”,然后通过PreparedStatement对象的setXXX()方法为参数占位符赋值,而${}参数占位符内容会被直接替换为参数值。

使用#{}参数占位符能够有效避免SQL注入问题,所以我们优先考虑使用#{}占位符,当#{}参数占位符无法满足需求时,才考虑使用${}参数占位符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

弯_弯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值