MyBatis中的#{} 和 ${}

目录

#{} 和 ${}

预编译 SQL 和 即时 SQL

SQL注入

${}的使用


#{} 和 ${}的使用

MyBatis参数赋值有两种方式,在上一篇文章中,一直使用 #{} 进行赋值,接下来,我们来使用 ${} 进行赋值,并观察 #{} 和 ${} 的区别

使用#{}进行赋值:

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

测试并观察结果:

    @Test
    void selectById() {
        System.out.println(userInfoMapper.selectById(2));
    }

 使用 ${} 进行赋值

    @Select("select * from userinfo where id = ${id}")
    public UserInfo selectById2(Integer id);

测试并观察结果:

    @Test
    void selectById2() {
        System.out.println(userInfoMapper.selectById2(2));
    }

 对比两次运行的结果:

我们可以发现:当我们使用 #{} 时,输入的参数并未在后面之间拼接,而是使用 ? 进行占位,这种SQL称之为"预编译SQL",而当使用 ${} 时,输入的参数直接拼接在后面

当我们传递 String 类型的参数时

    @Select("select * from userinfo where username = #{username}")
    public List<UserInfo> selectByUsername(String username);
    
    @Select("select * from userinfo where username = ${username}")
    public List<UserInfo> selectByUsername2(String username);

分别进行测试:

    @Test
    void selectByUsername() {
        System.out.println(userInfoMapper.selectByUsername("zhangsan"));
    }

    @Test
    void selectByUsername2() {
        System.out.println(userInfoMapper.selectByUsername2("zhangsan"));
    }

 观察结果:

我们可以发现:

 当使用 ${} 时,由于参数直接拼接在SQL语句中,而字符串作为参数时需要添加 '',而 ${} 不会拼接'',因此程序报错

我们可以修改代码:

    @Select("select * from userinfo where username = '${username}'")
    public List<UserInfo> selectByUsername2(String username);

 此次成功返回结果

通过上述的对比分析,我们可以发现:

#{} 使用的是预编译SQL,通过 ? 占位的方式,提取对SQL进行编译,然后将参数填充到 SQL 语句中,且 #{} 会通过参数类型,自动拼接引号 ''

${} 会直接进行字符串替换,然后再一起对 SQL 进行编译,当参数为字符串时,需要添加上 ''

(参数为数字类型时也可以加,查询结果不变,但可能导致索引失效,性能下降)

#{} 和 ${} 的区别就是 预编译SQL 和 即时SQL 的区别

预编译 SQL 和 即时 SQL

预编译 SQL(Prepared SQL):

在预编译阶段,数据库管理系统(DBMS)会对 SQL 语句进行编译,生成执行计划,并将其存储在数据库中,而不是在每次执行时重新编译。

在执行阶段,应用程序发送带有占位符的预编译 SQL 语句给 DBMS,DBMS 根据预编译的执行计划执行 SQL,并将实际参数传递给占位符,以生成最终的查询结果。

即时 SQL(Immediate SQL) :

即时 SQL 是指每次执行 SQL 语句时,数据库管理系统都会即时地进行语法分析、语义分析、优化和执行,而不是提前进行编译和优化。

每次执行即时 SQL 语句都会有一定的性能开销,因为需要进行完整的编译和优化过程。

当服务器接收到一条 SQL 语句后:

先解析语法和语义,校验 SQL 语句是否正确

再优化 SQL 制定执行计划

执行并返回结果

一条 SQL 若执行上述流程,我们就称为 Immediate Statements(即时SQL)

而预编译 SQL,则会在编译一次之后将编译后的 SQL 语句缓存起来,后面再执行这条语句时,便不会再进行编译,省去了解析优化等过程

预编译 SQL 可以提高执行效率,因为 SQL 语句只需编译一次,然后可以多次执行,避免了重复编译的开销。预编译 SQL 通常用于需要频繁执行的 SQL 语句,如重复执行的查询或更新操作

即时 SQL 通常用于不经常执行或每次执行的 SQL 语句

即,预编译 SQL 可以提高执行效率和性能,适用于重复执行的 SQL 语句,而即时 SQL 则更灵活,适用于临时性或不经常执行的 SQL 操作。

因此,相比于使用 ${},#{} 的性能更高,且 ${} 会有SQL注入的风险

SQL注入

SQL注入:通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

当使用 ${} 时,由于没有对用户输入进行充分检查,而SQL又是拼接而成的,在用户输入参数时,在参数中添加一些 SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

我们以 selectByUsername() 来进一步理解 SQL 注入:

当我们传递参数:

    @Test
    void selectByUsername2() {
        System.out.println(userInfoMapper.selectByUsername2("' or 1='1"));
    }

测试结果:

此时参数 or 被当作 SQL语句的一部分, 查询条件变为 username = ' ' or 1 = '1',1 = ‘1’ 恒成立,因此 查询出所有数据

我们再以用户登录的例子来看:

UserController:

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired private UserService userService;
    @RequestMapping("/login")
    public boolean login(String username, String password){
        //参数校验
        if(!StringUtils.hasLength(username)
                || !StringUtils.hasLength(password)){
            return false;
        }
        UserInfo userInfo = userService.selectUserByPassword(username, password);
        if(userInfo == null){
            return false;
        }
        return true;
    }
}

UserService:

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public UserInfo selectUserByPassword(String username, String password) {
        List<UserInfo> userInfos = userInfoMapper.selectUserByPassword(username, password);
        System.out.println(userInfos);
        if(userInfos != null && userInfos.size() > 0){
            return userInfos.get(0);
        }
        return null;
    }
}

UserInfoMapper:

@Mapper
public interface UserInfoMapper {
    @Select("select * from userinfo where username = '${username}' and password = '${password}'")
    List<UserInfo> selectUserByPassword(String username, String password);
}

若在用户登录时,输入密码 ' or 1 = '1,也就有可能完成登录

运行并访问:

http://127.0.0.1:8080/user/login?username=zhangsan&password= ' or 1 = '1

结果:

此时虽然密码错误,但是仍能登录

SQL注入是一种常见的数据库攻击手段,SQL注入漏洞也是网络世界中最为普遍的漏洞之一

${} 会有 SQL 注入的风险,所以我们尽量使用 #{} 进行查询,

既然已经有 #{} ,那 ${} 是否就没必要使用了呢?

当然不是,在一些特定的场景下,${} 能够完成

${}的使用

当我们需要按照升序或是降序对查询的数据进行排序时:

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

测试:

    @Test
    void selectAllUserBySort() {
        userInfoMapper.selectAllUserBySort("desc");
    }

而当我们使用 #{}

    @Select("select * from userinfo order by id #{sort}")
    List<UserInfo> selectAllUserBySort(String sort);

由于 参数类型为 String,desc 被自动加上了 '',因此导致 sql 错误

 同理,当我们使用 like 查询时:

    @Select("select * from userinfo where username like '%${key}%'")
    List<UserInfo> selectAllUserByLike(String key);

测试:

    @Test
    void selectAllUserByLike() {
        userInfoMapper.selectAllUserByLike("zhang");
    }

而当我们使用 #{} 时:

使用 #{} 时会报错,我们可以通过 mysql的内置函数 concat() 进行拼接,从而解决问题:

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

由于 ${} 存在 SQL注入的问题,因此,在能够使用 #{} 的情况下,我们尽可能选择使用 #{},而在需要使用 ${} 时,我们需要对用户输入的数据进行验证,确保其符合预期的格式和范围

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588412.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年外贸企业邮箱最新排名

外贸企业在与客户沟通中的重要工具就是企业邮箱&#xff0c;那么外贸企业邮箱哪个比较好呢&#xff1f;本文聚焦2024年外贸企业邮箱市场的最新动态&#xff0c;通过对五个领先品牌的深度对比&#xff0c;旨在为企业决策者提供详尽参考。首当其冲的是备受瞩目的Zoho Mail企业邮箱…

JMeter性能压测脚本录制

第一步&#xff1a;电脑打开控制面板设置代理服务器 第二步&#xff1a;jmeter的测试计划添加一个HTTP&#xff08;S&#xff09;脚本记录器 在脚本记录器里配置好信息&#xff0c;然后保存为脚本文件&#xff08;.*表示限定&#xff09; 此方框内容为项目地址&#xff08;可改…

字符串函数与字符函数运用(1)

字符串与字符函数介绍1 前言一、字符分类函数字符函数练习 二、字符函数转换1.引入库2.代码改进 字符串函数strlen函数strcpy 结尾 前言 字符串函数大概有以下这几种 strcpy、strcat 、strcmp、strncpy、strncat、strncmp、strstr、strtok、strerror 这些函数可以很好的解决你…

DRF中的请求入口分析及request对象分析

DRF中的请求入口分析及request对象分析 django restframework框架是在django的基础上又给我们提供了很多方便的功能&#xff0c;让我们可以更便捷基于django开发restful API 1 drf项目 pip install django pip install djangorestframework1.1 核心配置 INSTALLED_APPS [d…

神经网络中常见的激活函数:理解与实践

神经网络中常见的激活函数&#xff1a;理解与实践 在神经网络中&#xff0c;激活函数是一个非常重要的组成部分&#xff0c;它为神经元引入了非线性特性&#xff0c;使得神经网络可以拟合各种复杂的函数关系。本文将介绍9种常见的激活函数&#xff0c;包括它们的概述、公式以及…

《百图解码支付系统设计与实现》电子书_V20240503

《百图解码支付系统设计与实现》这本书的底稿已经完成一半&#xff0c;从2023.12.24发布专栏第一篇文章“跟着图走&#xff0c;学支付&#xff1a;在线支付系统设计的图解教程”算起&#xff0c;陆续写了30来篇支付相关的干货。 本书是我的专栏《百图解码支付系统设计与实现》…

基于Spring Boot的校园闲置物品交易网站设计与实现

基于Spring Boot的校园闲置物品交易网站设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看…

springboot项目组合定时器schedule注解实现定时任务

springboot项目组合定时器schedule注解实现定时任务&#xff01; 创建好springboot项目后&#xff0c;需要在启动类上增加注解开启定时器任务 下图所示&#xff1a; 增加这个注解&#xff0c;启动项目&#xff0c; package com.example.scheduledemo.util;import org.springf…

C++中的异常

目录 1.C语言传统的处理错误的方式 2. C异常概念 3. 异常的使用 3.1 异常的抛出和捕获 3.2 异常的重新抛出 3.3异常安全 3.4 异常规范 4.自定义异常体系 5.C标准库的异常体系 6.异常的优缺点 7.func&#xff08;&#xff09; throw();的方式规范化 1.C语言传统的处理…

C语言字符串(0基础到深入剖析)---字符串系列合集(函数+指针+数组)

前言 本篇旨在帮助不了解字符串或者逻辑梳理不够透彻的伙伴们理出一条脉络。选择能看懂的部分即可&#xff0c;建议收藏&#xff0c;后期学习完C语言方便回顾。 适用范围&#xff1a;0基础C语言&#xff08;刚学字符串&#xff09;- 学过函数 - 学过指针 ---大致了解了数据内…

Centos7 安装Git、使用

Centos7 安装Git 一、安装步骤1.1 查看版本1.2 卸载1.3 安装 二、创建仓库2.1 新增仓库2.2 新增配置项 三、管理文件3.1 文件创建3.2 文件修改、add、commit3.3 tree结构探索 四、分支4.1 创建分支&#xff1a;4.2 查看分支4.3 切换分支4.4 删除分支4.5 合并冲突 一、安装步骤 …

FusionMamba: Efficient Image Fusion with State Space Model【文献阅读】

论文&#xff1a;FusionMamba&#xff1a;一种基于SSM的有效图像融合方法 arXiv&#xff1a;https://arxiv.org/abs/2404.07932 作者单位&#xff1a;中国科学院自动化研究所、模式识别重点实验室、电子科技大学 推荐阅读&#xff1a;深入浅出一文图解Vision Mamba Abstract 图…

3.自动驾驶-局部路径规划

1. 规划planning 2. 局部路径规划模块实现-模块外围&#xff1a;输入 3. 局部路径规划模块实现模块外围:输出 4. 控制control 5. 系统分类 6 系统分类

C 认识指针

目录 一、取地址操作符&#xff08;&&#xff09; 二、解引用操作符&#xff08;*&#xff09; 三、指针变量 1、 指针变量的大小 2、 指针变量类型的意义 2.1 指针的解引用 2.2 指针 - 整数 2.3 调试解决疑惑 认识指针&#xff0c;指针比较害羞内敛&#xff0c;我们…

自定义SpringBoot的starter

案例需求&#xff1a;自定义redis-stater。要求当导入redis坐标时&#xff0c;SpringBoot自动创建Jedis的Bean。 实现步骤&#xff1a; 1、创建redis-spring-boot-autoconfigure模块 2、创建redis-spring-boot-starter模块&#xff0c;依赖redis-spring-boot-autoconfigure的…

Android 文件传输

经常写adb命令传文件&#xff0c;结果发现Android studio有自带的文件管理器&#xff0c;可以上传下载文件。

程序包的创建

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 前面很多范例中都用到的 dbms output.put_line 实际上就是一个典型的程序包应用&#xff0c; 其中 dbms output是程序包的名称&#xff0c;put_line 是该程序包中定义的一个…

碳纤维复合材料的纳米纤维膜

碳纤维复合材料的纳米纤维膜是一种具有良好性能和应用前景的新材料。以下是关于这种材料的详细介绍&#xff1a; 制备方法&#xff1a;碳纤维复合材料的纳米纤维膜可以通过多种方法制备&#xff0c;包括化学气相沉积法、固相合成法、模板法等。其中&#xff0c;化学气相沉积法是…

十三、大模型项目部署与交付

1 硬件选型 CUDA 核心和 Tensor 核心 CUDA 核心&#xff1a;是NVIDIA开发的并行计算平台和编程模型&#xff0c;用于GPU上的能用计算&#xff0c;可做很多的工作。应用在游戏、图形渲染、天气预测和电影特效Tensor 核心&#xff1a;张量核心&#xff0c;专门设计用于深度学习…

YOLOv5入门(四)训练自己的目标检测模型

前言 通过前面几篇文章&#xff0c;已经完成数据集制作和环境配置&#xff08;服务器&#xff09;&#xff0c;接下来将继续实践如何开始训练自己数据集~ 往期回顾 YOLOv5入门&#xff08;一&#xff09;利用Labelimg标注自己数据集 YOLOv5入门&#xff08;二&#xff09;处…