专栏首页苏三说技术面试官:mybatis中#{ }和${ }的区别

面试官:mybatis中#{ }和${ }的区别

关注“苏三说技术”,回复:代码神器、开发手册、时间管理 有惊喜。

如果有用过mybatis的朋友,肯定对#{ }非常熟悉。

让我们先一起看看#{ }的用法。

数据库中有2条数据,如图:

我们先定义一个实体:

@Data
public class JumpLogModel {

    /**
     * 系统ID
     */
    private String id;

    /**
     * 应用编号
     */
    private String app;

    /**
     * 跳转url
     */
    private String url;

    /**
     * ip地址
     */
    private String ip;

    /**
     * 区域名称
     */
    private String areaName;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 浏览器
     */
    private String browser;

    /**
     * 来源
     */
    private String refer;

    /**
     * 操作时间
     */
    private Date inDate;

}

然后定义mapper:

public interface JumpLogService {
    JumpLogModel selectById(String id);
}

再定义xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sue.jump.mappers.JumpLogMapper">
    
    <resultMap type="com.sue.jump.model.JumpLogModel" 
     id="JumpLogResult">

        <result property="id"         column="id"/>
        <result property="app"        column="app"/>
        <result property="url"        column="url"/>
        <result property="ip"         column="ip"/>
        <result property="areaName"   column="area_name"/>
        <result property="os"         column="os"/>
        <result property="browser"    column="browser"/>
        <result property="inDate"     column="in_date"/>
    </resultMap>

    <select id="selectById" resultMap="JumpLogResult">
        select
           id,app,url,ip,area_name,os,browser
        from jump_log
        <where>
            id = #{id}
        </where>
    </select>
</mapper>

定义service层:

@Service
public class JumpLogServiceImpl implements JumpLogService {

    @Autowired
    private JumpLogMapper jumpLogMapper;


    @Override
    public JumpLogModel selectById(String id) {
        return jumpLogMapper.selectById(id);
    }
}

定义controller层

@RequestMapping("/jump")
@RestController
public class JumpLogController {

    @Autowired
    private JumpLogServiceImpl jumpLogService;

    @GetMapping("/get/{id}")
    public JumpLogModel get(@PathVariable String id) {
        return jumpLogService.selectById(id);
    }
}

调用接口,id=123456

我们看到可以通过id查询到正确的数据,说明#{ }生效了。

那么我们把#{ },改成${ }再试试。

<select id="selectById" resultMap="JumpLogResult">
        select
          id,app,url,ip,area_name,os,browser
        from jump_log
       <where>
            id = ${id}
        </where>
</select>

再调用接口,id=123456

同样可以根据id查询出正确的数据。那么有人可能会说,#{ } 和 ${ }不是一样吗?二者有什么区别呢?

接下来,我们重点看看二者的区别。

现有#{ }接收参数

<select id="selectById" resultMap="JumpLogResult">
      select
             id,app,url,ip,area_name,os,browser
      from jump_log
      <where>
            id = #{id}
        </where>
</select>

把id的值由123456改成:123456 or 1=1,再调用接口

依然可以返回正确的数据。

再改成${ }接收参数

<select id="selectById" resultMap="JumpLogResult">
        select
          id,app,url,ip,area_name,os,browser
    from jump_log
    <where>
         id = ${id}
    </where>
</select>

报错了。。。。。。

提示了:Expected one result (or null) to be returned by selectOne(), but found: 2

通过id原本只能返回第1条数据,结果返回了2条数据。怎么回事?

原来通过${ }接收参数之后,最后拼接的sql如下:

select id,app,url,ip,area_name,os,browser from jump_log where id = 123456 or 1=1

明白了,这是典型的sql注入,后面的 or 1=1 会让前面的 id=123456条件失效,相当于整个where条件都失效了,最后sql相当于执行了:

select id,app,url,ip,area_name,os,browser from jump_log

肯定会返回2条数据。

那么问题来了,#{ }的方式为什么没有问题呢?

因为#{ }接收参数使用了sql预编译,最后拼接的sql会变成:

select id,app,url,ip,area_name,os,browser from jump_log where id = ?

执行sql时会将参数进行转义,把传入的参数:123456 or 1=1加了单引号',执行时的sql是:

select id,app,url,ip,area_name,os,browser from jump_log where id = '123456 or 1=1'

可以正确返回1条数据。

我们可以得出结论,#{ } 通过预编译可以防止sql注入。

那是不是在实际开发中都用#{ }就好了,不需要使用${ }了?

其实,不然,

比如有这样的场景:数据库的名称需要通过参数统一起来,以便下次修改数据库名时,只有修改一个地方即可。

在mybatis-config.xml文件中配置:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties>
        <property name="mallDbName" value="sue_mall_db"/>
    </properties>
</configuration>

xml中使用

<select id="selectById" resultMap="JumpLogResult">
        select
             id,app,url,ip,area_name,os,browser
        from ${mallDbName}.jump_log
        <where>
            id = #{id}
        </where>
</select>

接下来,我们分析一下源码,看看#{}是怎么替换成?的

先看看XMLMapperBuilder类的configurationElement方法。

重点看看buildStatementFromContext方法:

会调用XMLStatementBuilder类的parseStatementNode方法:

进入LawLanguageDriver类的createSqlSource方法:

我们一起看看XmlScriptBuilder类的parseScriptNode方法:

看看this方法,即下面的构造方法:

最终我们会发现在SqlSourceBuilder类的GenericTokenParser解析器就是把#{} 符合 替换 为 ?占位符

总结一下:

${ } 直接的 字符串 替换,在mybatis的动态 SQL 解析阶段将会进行变量替换。

#{ } 通过预编译,用占位符的方式传值可以把一些特殊的字符进行转义,这样可以防止一些sql注入。

大家喜欢这篇文章的话,请关注一下 :苏三说技术

本文分享自微信公众号 - 苏三说技术(gh_9f551dfec941),作者:因为热爱所以坚持ing

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 天天在用volatile,你知道它的底层原理吗?

    对于从事java开发工作的朋友来说,在工作中可能会经常接触volatile关键字。即使有些朋友没有直接使用volatile关键字,但是如果使用过:Concurr...

    苏三说技术
  • 线程池最佳线程数量到底要如何配置?

    对于从事后端开发的同学来说,线程是必须要使用了,因为使用它可以提升系统的性能。但是,创建线程和销毁线程都是比较耗时的操作,频繁的创建和销毁线程会浪费很多CPU的...

    苏三说技术
  • 并发环境下,先操作数据库还是先操作缓存?

    来源:https://mp.weixin.qq.com/s/2ZvPScfbpl85ZGCDbifY1w

    苏三说技术
  • Mybatis之ResultMap

    爱撒谎的男孩
  • mongoose实现批量删除的api/方法

    蓓蕾心晴
  • 一次夜维SQL的性能优化

    最近单位搬家,从国家会议中心,搬往空气清新的顺义后沙峪,搬迁之前的完结上线中,碰见了一些棘手的问题,有一些值得借鉴的地方。

    bisal
  • 第26天:js-$id函数、焦点事件

    一、函数return语句 定义函数的返回值,在函数内部用return来设置返回值,一个函数只能有一个返回值。同时,终止代码的执行。 所有自定义函数默认没有返回值...

    半指温柔乐
  • 约束

    一 介绍 约束条件与数据类型的宽度一样,都是可选参数 作用:用于保证数据的完整性和一致性 主要分为: PRIMARY KEY (PK) 标识该字段为该表的...

    用户1214487
  • 【leetcode两题选手】MySQL类题目(八)

    某城市开了一家新的电影院,吸引了很多人过来看电影。该电影院特别注意用户体验,专门有个 LED显示板做电影推荐,上面公布着影评和相关电影描述。

    看、未来
  • springmvc实例之修改雇员相关信息(四)

    首先是在EmployeeHandler.java中编写toEditEmployeePage方法:

    绝命生

扫码关注云+社区

领取腾讯云代金券