前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:mybatis中#{ }和${ }的区别

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

作者头像
苏三说技术
发布2020-10-15 14:42:32
5150
发布2020-10-15 14:42:32
举报
文章被收录于专栏:苏三说技术

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

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

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

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

我们先定义一个实体:

代码语言:javascript
复制
@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:

代码语言:javascript
复制
public interface JumpLogService {
    JumpLogModel selectById(String id);
}

再定义xml文件:

代码语言:javascript
复制
<?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层:

代码语言:javascript
复制
@Service
public class JumpLogServiceImpl implements JumpLogService {

    @Autowired
    private JumpLogMapper jumpLogMapper;


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

定义controller层

代码语言:javascript
复制
@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查询到正确的数据,说明#{ }生效了。

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

代码语言:javascript
复制
<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查询出正确的数据。那么有人可能会说,#{ } 和 ${ }不是一样吗?二者有什么区别呢?

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

现有#{ }接收参数

代码语言:javascript
复制
<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,再调用接口

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

再改成${ }接收参数

代码语言:javascript
复制
<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文件中配置:

代码语言:javascript
复制
<?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中使用

代码语言:javascript
复制
<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注入。

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 苏三说技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档