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

news2025/2/24 4:52:36

 

目录

前言

预编译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.coloradmin.cn/o/2304195.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙开发环境搭建-入门篇

本文章讲述如何搭建鸿蒙应用开发环境&#xff1a;新建工程、虚拟机运行、真机调试等。 开发工具: DevEco Studio 5.0.3.906 os系统: mac 参考文档&#xff1a;https://juejin.cn/post/7356143704699699227 官网鸿蒙应用开发学习文档&#xff1a;https://developer.huawei.com/c…

iOS开发 网络安全

iOS开发中的网络安全 在当前的数字化时代&#xff0c;任何应用程序都需要重视网络安全。尤其是对于iOS应用开发者而言&#xff0c;确保应用与服务器之间的数据传输安全是至关重要的。接下来&#xff0c;我们将学习“iOS开发 网络安全”的实现过程。 流程步骤 以下是实现iOS网…

MATLAB在投资组合优化中的应用:从基础理论到实践

引言 投资组合优化是现代金融理论中的核心问题之一&#xff0c;旨在通过合理配置资产&#xff0c;实现风险与收益的最佳平衡。MATLAB凭借其强大的数学计算能力和丰富的金融工具箱&#xff0c;成为投资组合优化的理想工具。本文将详细介绍如何使用MATLAB进行投资组合优化&#…

银河麒麟系统安装mysql5.7【亲测可行】

一、安装环境 cpu&#xff1a;I5-10代&#xff1b; 主板&#xff1a;华硕&#xff1b; OS&#xff1a;银河麒麟V10&#xff08;SP1&#xff09;未激活 架构&#xff1a;Linux 5.10.0-9-generic x86_64 GNU/Linux mysql版本&#xff1a;mysql-5.7.34-linux-glibc2.12-x86_64.ta…

自动创建spring boot应用(eclipse版本)

使用spring starter project创建项目 设置Service URL 把Service URL设置为 https://start.aliyun.com/ 如下图&#xff1a; 使用这个网址&#xff0c;创建项目更快。 选择Spring Web依赖 项目结构 mvnw和mvnw.cmd:这是maven包装器&#xff08;wrapper&#xff09;脚本&…

基于Flask的第七次人口普查数据分析系统的设计与实现

【Flask】基于Flask的第七次人口普查数据分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 基于Flask的人口普查可视化分析系统 二、项目界面展示 登录/注册 首页/详情 …

Linux:文件(三)

1. 磁盘 基本概念 机械磁盘在现在的计算机中基本是唯一的一个机械设备 速度较内存更慢&#xff0c;容量大价格便宜。 磁盘是永久性存储介质&#xff0c;断电后数据还在。 内存是易失性存储介质&#xff0c;断电后(未写入磁盘的)数据丢失。 物理存储结构 扇区&#xff1a;…

DeepSeek 给我一个 DeepSeekUI 页面

接着上次分享内容 三步安装 DeepSeek 说&#xff0c;DeepSeek 下载好了&#xff0c;总不能是黑框框对话吧&#xff0c;总得找一个 UI 界面使用吧。 本地运行 DeepSeek 比安装 python、jdk 简单多了&#xff0c;本地还没装过的可以参考上次的文档安装。 于是找了几个开源的试了试…

Java NIO与传统IO性能对比分析

Java NIO与传统IO性能对比分析 在Java中&#xff0c;I/O&#xff08;输入输出&#xff09;操作是开发中最常见的任务之一。传统的I/O方式基于阻塞模型&#xff0c;而Java NIO&#xff08;New I/O&#xff09;引入了非阻塞和基于通道&#xff08;Channel&#xff09;和缓冲区&a…

小智机器人CMakeLists编译文件解析

编译完成后&#xff0c;成功烧录&#xff01; 这段代码是一个CMake脚本&#xff0c;用于配置和构建一个嵌入式项目&#xff0c;特别是针对ESP32系列芯片的项目。CMake是一个跨平台的构建系统&#xff0c;用于管理项目的编译过程。 set(SOURCES "audio_codecs/audio_code…

【科研绘图系列】R语言绘制SCI论文图合集

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载Load dataFigure 1Fig 1B: functional assays adhensionFIG 1C: Functional assays OPK Figure 2Fig 2C: Settings and function fo…

VSCode ssh远程连接内网服务器(不能上网的内网环境的Linux服务器)的终极解决方案

VSCode ssh远程连接内网服务器&#xff08;不能上网的内网环境的Linux服务器&#xff09; 离线下载vscode-server并安装: 如果远程端不能联网可以下载包离线安装,下载 vscode-server 的 url 需要和 vscode 客户端版本的 commit-id 对应.通过 vscode 面板的帮助->关于可以获…

支持向量机(SVM):算法讲解与原理推导

1 SVM介绍 SVM是一个二类分类器&#xff0c;它的全称是Support Vector Machine&#xff0c;即支持向量机。 SVM的目标是找到一个超平面&#xff0c;使用两类数据离这个超平面越远越好&#xff0c;从而对新的数据分类更准确&#xff0c;即使分类器更加健壮。比如上面的图中&am…

macos sequoia 禁用 ctrl+enter 打开鼠标右键菜单功能

macos sequoia默认ctrlenter会打开鼠标右键菜单&#xff0c;使得很多软件有冲突。关闭方法&#xff1a; end

Android14 Camera框架中Jpeg流buffer大小的计算

背景描述 Android13中&#xff0c;相机框架包含对AIDL Camera HAL的支持&#xff0c;在Android13或更高版本中添加的相机功能只能通过AIDL Camera HAL接口使用。 对于Android应用层来说&#xff0c;使用API34即以后版本的Camera应用程序通过Camera AIDL Interface访问到HAL层…

springboot系列十四: 注入Servlet, Filter, Listener + 内置Tomcat配置和切换 + 数据库操作

文章目录 注入Servlet, Filter, Listener官方文档基本介绍使用注解方式注入使用RegistrationBean方法注入DispatcherServlet详解 内置Tomcat配置和切换基本介绍内置Tomcat配置通过application.yml完成配置通过类配置 切换Undertow 数据库操作 JdbcHikariDataSource需求分析应用…

区块链共识机制详解

区块链共识机制详解 &#x1f91d; 1. 什么是共识机制&#xff1f; 共识机制是区块链网络中&#xff0c;所有节点就某个状态&#xff08;如交易的有效性&#xff09;达成一致的规则和过程。它解决了在去中心化网络中如何确保数据一致性的问题。 2. 主流共识机制 2.1 工作量证…

详解单例模式、模板方法及项目和源码应用

大家好&#xff0c;我是此林。 设计模式为解决特定问题提供了标准化的方法。在项目中合理应用设计模式&#xff0c;可以避免重复解决相同类型的问题&#xff0c;使我们能够更加专注于具体的业务逻辑&#xff0c;减少重复劳动。设计模式在定义系统结构时通常考虑到未来的扩展。…

解耦的艺术_应用架构中的解耦

文章目录 Pre解耦的技术演化应用架构中的解耦小结 Pre 解耦的艺术_通过DPI依赖倒置实现解耦 解耦的艺术_通过中间层映射实现解耦 解耦的技术演化 技术的演化史&#xff0c;也是一部解耦的历史。从最初的面向对象编程&#xff08;OOP&#xff09;到Spring框架的依赖注入&…

Winform(C#) 项目保存页面

上一张我们已经实现了TCP和串口页面的数据展示&#xff0c;和保存控件 我们这一章&#xff0c;实现如何去&#xff0c;控制保存。 一、控件展示 CheckBox TextBox Button label Name: checkSaveImage checkDelete txtSaveDays txtSaveImagePath btnSelectIm…