当前位置: 首页 > news >正文

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

 

目录

前言

预编译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() 来解决。


以上就是本篇所有内容~

若有不足,欢迎指正~

相关文章:

  • Vue.js 与 Ajax(Axios)的深入探索
  • 力扣——划分字母区间
  • 实验 Figma MCP + Cursor 联合工作流
  • (南京观海微电子)——码片与码元之间的关系
  • MATLAB中random函数用法
  • 功能全面的手机壁纸应用,种类齐全、众多高清壁纸
  • OpenHarmony包管理子系统
  • Linux lsblk 命令详解:查看磁盘和分区信息 (中英双语)
  • Spring Boot Validation 接口校验:从零到掌握
  • python中轻量级 LLM 应用开发框架 Promptic 如何有效进行对话管理?
  • AI革命下的多元生态:DeepSeek、ChatGPT、XAI、文心一言与通义千问的行业渗透与场景重构
  • 基于Hadoop的汽车大数据分析系统设计与实现【爬虫、数据预处理、MapReduce、echarts、Flask】
  • DPVS-1:编译安装DPVS (ubuntu22.04)
  • DeepSeek-R1之三_基于RAGFlowAI托管平台在Docker中部署搭建本地AI知识库
  • 关于java中的BigInteger类
  • 区块链相关方法-波特五力分析模型
  • Eigen3开发入门指南:矩阵操作与核心功能详解
  • ChromeDriver版本不匹配问题的解决
  • Android Binder机制
  • Java 继承
  • 上海“电子支付费率成本为0”背后:金融服务不仅“快”和“省”,更有“稳”和“准”
  • 2025柯桥时尚周启幕:国际纺都越来越时尚
  • 71岁导演詹姆斯・弗雷病逝,曾执导《纸牌屋》、麦当娜MV
  • 巴基斯坦信德省卡拉奇发生爆炸
  • 世界人形机器人运动会将在北京“双奥场馆”举行
  • 外交部:中方和欧洲议会决定同步全面取消对相互交往的限制