【MyBatis】#{} 与 ${} 的区别(常见面试题)

news/2025/2/24 4:07:41

 

目录

前言

预编译SQL和即时SQL

什么是预编译SQL?

什么是即时SQL?

区别

#{} 与 ${}的使用

防止SQL注入

什么是SQL注入?

原理

排序功能

模糊查询 

总结#{}和${}的区别


前言

在前面的学习中,我们已经知道了如果SQL语句想要获取到方法中的参数,需要使用#{} 来进行获取,但是在MyBatis中,还有另外一种方式可以获取到方法中的参数,使用 ${}

那么这两种方式叫什么呢?

预编译SQL和即时SQL

#{} 是预编译SQL${} 是即时SQL

什么是预编译SQL?

预编译SQL是指在程序编译阶段或首次执行时预先编译好的SQL语句,通常通过参数化的方式来传递数据。

什么是即时SQL?

即时SQL指的是在程序运行时动态生成执行的SQL语句,通过是在程序中根据用户输入或者其他运行时条件拼接而成的SQL语句。

区别

  • 性能:

    • 即时SQL每次执行都需要先解析和优化SQL语句,性能相对较低

    • 预编译SQL是在首次执行时完成解析和优化,并且后序执行可以直接复用已编译好的执行计划,性能更高。

  • 安全性:

    • 即时SQL容器受到SQL注入攻击,如果用户输入的内容被直接拼接到SQL语句中,恶意用户可以构造特殊的输入来破坏SQL,比如在后面加delete 数据库。

    • 预编译SQL通过参数化的方式来传递数据,避免了SQL注入风险。这是因为预编译SQL不会将用户输入直接拼接到SQL语句中,而是作为参数传递,数据库会将其视为数据。

  • 灵活性:

    • 即时SQL灵活性较高,可以根据运行时的条件动态生成SQL语句,适合复杂的业务逻辑和动态查询。比如对数据进行排序等。

    • 预编译SQL灵活性较低,因为SQL语句的结构是在预编译阶段已经确定,不能根据运行时条件动态调整SQL语句的结构。

  • 使用场景:

    • 即时SQL适合SQL语句结构复杂,需要根据用户动态生成SQL语句的场景,例如复杂的查询条件、动态排序等。

    • 预编译SQL适用于SQL语句固定,需要频繁执行的场景,例如插入、更新、删除等操作。

总的来说:即时SQL适用动态生成SQL语句的场景,但需要注意SQL注入攻击;而预编译SQL适用固定结构的SQL语句,具有更高的性能和安全性

#{} 与 ${}的使用

在使用前,别忘记配置数据库的相关配置。

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

接下来我们就来学习在Mybatis中使用这两种方式,这里我们采用注解的方式:

#{} 写法:

    @Select("select * from user_info where id = #{userId}")
    public UserInfo selectById(@Param("userId") Integer id);

   ${}写法:

    @Select("select * from user_info where id = ${userId}")
    public UserInfo selectById(@Param("userId") Integer id);

 测试代码:

   @Test
    void selectById() {
        UserInfo userInfo = userInfoMapper.selectById(1);
        System.out.println(userInfo);
    }

 

我们可以看到,利用#{} 来进行传递参数时,不会将参数直接拼接到SQL语句中,而是会用 ? 进行占位,这种就是我们说的预编译SQL

而使用 ${} 来传递参数时,会直接将传递的参数拼接到SQL语句中,这种就是即时SQL

在上面中,我们是根据Integer类型来进行查询, 假如我们传递的是String 类型,是否也能查询成功?

    @Select("select * from user_info where username = #{userName}")
    public UserInfo selectByUserName(String userName);
 @Test
    void selectByUserName() {
        UserInfo userInfo = userInfoMapper.selectByUserName("小红");
        System.out.println(userInfo);
    }

 

可以看到,使用 #{} 在传递参数的时候,会根据传递的参数的类型来进行识别,这里我们传递的是String类型。我们再来看下${}。 

    @Select("select * from user_info where username = ${userName}")
    public UserInfo selectByUserName(String userName);

 

可以看到,在上面的SQL语句中,是直接将参数拼接到了SQL的末尾,且没有加引号。在SQL中,如果没有加引号的字符串会被认为是表中的列,而我们的表中没有列名为小红的,所以就会报错。

那么应该如何解决呢?我们可以在 ${} 进行参数传递的时候将引号添加上即可

    @Select("select * from user_info where username = '${userName}' ")
    public UserInfo selectByUserName(String userName);

再运行一次:

总结: 使用预编译SQL#{},是通过占位的,提前对SQL进行编译,再把参数填充到SQL语句中。而使用即时SQL ${},是直接进行字符的替换,再一起对SQL进行解析优化,如果参数是字符串,那么我们需要手动在SQL中加上引号。

防止SQL注入

前面我们说了预编译SQL除了提高效率,还能防止SQL注入攻击,那什么是SQL注入呢?

什么是SQL注入?

SQL注入是一种常见的网络安全攻击手段,攻击者通过在应用程序的输入字段中插入恶意的SQL代码片段,来篡改数据库查询语句的逻辑,从而实现非法访问、篡改、删除或窃取数据库中的数据。

原理

SQL注入的核心在于应用程序将用户输入直接拼接到SQL语句中,而没有对输入进行适当的验证、过滤或参数化处理。当攻击者精心构造输入时,这些输入会被错误地解释为SQL代码的一部分,从而改变SQL语句的原始逻辑。

例如:假设我们现在想要在数据库中查找小红,如果使用的是即时SQL。

    @Select("select * from user_info where username = '${userName}'")
    public UserInfo selectByUserName(String userName);
   @Test
    void selectByUserName() {
        UserInfo userInfo = userInfoMapper.selectByUserName("小红");
        System.out.println(userInfo);
    }

这个代码是可以正常运行的,但如果有人在后面加上  or '1'='1会怎么样?

  @Test
    void selectByUserName() {
        UserInfo userInfo = userInfoMapper.selectByUserName("小红' or '1'='1");
        System.out.println(userInfo);
    }

可以看到,如果在后面加上了  or '1'='1 ,那么这里就不仅会查找到我们想要的数据,还会将表中所有的数据都给显示出来,这样就会导致数据暴露。

那么我们使用预编译SQL #{} 会不会发生这样的情况呢?

    @Select("select * from user_info where username = #{userName}")
    public UserInfo selectByUserName(String userName);

可以看到,通过预编译SQL,我们输入的参数会被认为成 username 的一个名称,而不会被解析成前面的SQL,在表中没有叫 小红' or '1'='1 的数据,所以为0。可以看到,通过预编译SQL,能够有效的避免SQL注入的问题。

为了防止SQL注入问题,我们尽量使用 #{}

既然在性能和防止SQL注入方面,预编译SQL都比即时SQL好,那么是不是即时SQL就没用?

其实不是的,在某些情况下,比如排序方面,我们需要使用 ${} 来指定我们是要升序还是降序。

排序功能

我们来试下如果我们想要通过传参的方式来指定是升序还是降序,用 #{} 能不能解决。

    @Select("select * from user_info order by id #{sort}")
    public List<UserInfo> selectAllSort(String sort);
   @Test
    void selectAllSort() {
        List<UserInfo> list = userInfoMapper.selectAllSort("desc");
        list.forEach(System.out::println);
    }

在下面中, 可以看到,给出的原因是因为在 desc 上加了引号,而我们知道,在排序规则是不需要加上引号的,而在预编译占位的情况下,由于传递过去的是字符串,所以在拼接的时候会自动加上引号,对于这种情况,我们就需要用到即时SQL

    @Select("select * from user_info order by  id ${sort}")
    public List<UserInfo> selectAllSort(String sort);

模糊查询 

在前面MySQL的学习中,我们知道模糊查询是需要用 like 关键字%表示用来匹配任意数量的字符(包含0个), _ 用来匹配单个字符,那么如果我们想要用参数传递的方式来指定模糊查询的规则,就不能够使用 #{} 。

    @Select("select * from user_info where username like '%#{like}%'")
    public List<UserInfo> selectAllByLike(String like);
   @Test
    void selectAllByLike() {
        List<UserInfo> list = userInfoMapper.selectAllByLike("小");
        list.forEach(System.out::println);
    }
}

造成这样的结果:使用 #{} 传递,由于传递过去的是String类型,那么在进行SQL拼接的时候,就会自动加上引号,变成 '%'小‘%’.这样的SQL是错误的。 

所以我们还是需要使用 ${}

 但由于我们使用即时SQL,会有发生SQL注入风险,所以还是不太推荐使用 ${},我们可以使用SQL内置的函数 concat()

@Select("select * from user_info where username like concat ('%',#{like},'%')")
public List<UserInfo> selectAllByLike(String like);

总结#{}和${}的区别

  • 在性能和防止SQL注入方面预编译SQL比即时SQL要好
  • 使用 #{} 会在参数传递过来时,根据参数的类型,在拼接SQL时进行调整;而 ${} 则是直接进行SQL的拼接。
  • 排序时,传递排序规则需要使用 ${} ,使用 #{} 时,编译会根据传递过来的字符串类型,自动进行加引号。
  • 进行模糊查询时,需要使用 ${} ,但不推荐,虽然使用 #{} 会出问题,但我们使用SQL内置函数 concat() 来解决。


以上就是本篇所有内容~

若有不足,欢迎指正~


http://www.niftyadmin.cn/n/5863959.html

相关文章

draw.io:开源款白板/图表绘制利器

在工作和学习中&#xff0c;我们常常需要绘制各种图表&#xff0c;例如流程图、思维导图、网络拓扑图等等。一款功能强大且易于上手的图表绘制工具可以极大地提高我们的效率。今天&#xff0c;我要向大家推荐一款开源免费的图表绘制工具—— draw.io&#xff0c;并手把手教你如…

Linux 系统中的软链接与硬链接

目录 一、什么是软链接&#xff1f; 1. 创建软链接 2. 软链接的特性 3. 软链接的用途 二、什么是硬链接&#xff1f; 1. 创建硬链接 2. 硬链接的特性 3. 硬链接的用途 4. 目录硬链接的特殊性 ​编辑 三、软链接与硬链接的区别 1. inode 编号 2. 路径依赖 3. 删除行…

安卓好软-----车机端 安卓tv端很好用的应用管家 adb 授权等等功能

应用管家是一款完全免费且实用的安卓平台系统管理工具&#xff0c;专为管理电视、车机的应用及文件而设计。其支持提取/卸载禁用自带应用、传送安装、清理及本地文件查找编辑压缩等等功能。 众所周知&#xff0c;当前市面上大多数厂家智能电视系统均基于谷歌原生安卓进行了“深…

go channel 的用法和核心原理、使用场景

一、Channel 的核心用法 1. 基本操作 // 创建无缓冲 Channel&#xff08;同步通信&#xff09; ch : make(chan int) // 创建有缓冲 Channel&#xff08;容量为5&#xff0c;异步通信&#xff09; bufferedCh : make(chan int, 5) // 发送数据到 Channel ch <- 42 // 从…

(前端基础)CSS(一)

了解 Cascading Style Sheet&#xff1a;层叠级联样式表 CSS&#xff1a;表现层&#xff08;美化网页&#xff09;如&#xff1a;字体、颜色、边框、高度、宽度、背景图片、网页定位、网页浮动 css优势&#xff1a; 内容和表现分离网页结构表现统一&#xff0c;可以实现复用…

`AdminAdminDTO` 和 `userSession` 对象中的字段对应起来的表格

以下是将更正后的表格放在最前面的回答&#xff0c;表格包含序号列&#xff0c;合并了后端 AdminAdminDTO 和前端 userSession 的所有字段&#xff0c;并标注对方没有的字段。token 字段值用省略号&#xff08;...&#xff09;表示&#xff1a; 序号字段名AdminAdminDTO (后端…

抽象类、接口、枚举

一、抽象类 1.1 简介 作为父类&#xff0c;里面的方法逻辑不能满足任何一个子类的需求&#xff0c;提供的逻辑根本就用不上&#xff0c;那么就不添加方法体了&#xff0c;此时这个方法需要 使用关键字abstract来修饰&#xff0c;表示为抽象方法&#xff0c;而抽象方法所在的类…

Deepseek reasoning-content 透出调研

Deepseek reasoning-content 透出调研 部署方式&#xff1a;Docker Ollama Deepseek-R1:8b 参考&#xff1a; https://help.apiyi.com/deepseek-reasoning-content-guide.htmlhttps://yuluo-yx.github.io/blog/%E4%BD%BF%E7%94%A8-Ollama-%E9%83%A8%E7%BD%B2-DeepSeek-%E5…