MyBatis中的#{} 和 ${}

news2024/11/15 21:24:24

目录

#{} 和 ${}

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

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

相关文章

操作系统:线程

目录 前言&#xff1a; 1.线程 1.1.初识线程 1.2.“轻量化”进程 1.3.线程与进程 2.线程控制 2.1.pthread原生线程库 2.2.线程控制的接口 2.2.1.线程创建 2.2.线程退出|线程等待|线程分离|线程取消 2.3.pthread库的原理 2.4.语言和pthread库的关系 2.5.线程局部…

【leetcode】快慢指针相关题目总结

141. 环形链表 判断链表是否有环&#xff1a;如果链表中存在环&#xff0c;则在链表上不断前进的指针会一直在环里绕圈子&#xff0c;且不能知道链表是否有环。使用快慢指针&#xff0c;当链表中存在环时&#xff0c;两个指针最终会在环中相遇。 /*** Definition for singly-…

L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到一个处理错误。

一、首先这个问题&#xff0c;有一定概率出现&#xff08;已确认&#xff09; 1.使用后未将其断开或者频繁连接断开&#xff0c;导致注册表出现异常。&#xff08;目前推断是这样的&#xff09; 2.系统网卡驱动问题&#xff0c;需要进行网络重置&#xff0c;卸载网卡驱动后重新…

自动化机器学习——网格搜索法:寻找最佳超参数组合

自动化机器学习——网格搜索法&#xff1a;寻找最佳超参数组合 在机器学习中&#xff0c;选择合适的超参数是模型调优的关键步骤之一。然而&#xff0c;由于超参数的组合空间通常非常庞大&#xff0c;手动调整超参数往往是一项耗时且困难的任务。为了解决这个问题&#xff0c;…

基于Springboot的社区医疗服务系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区医疗服务系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

Unity LineRenderer 入门

概述&#xff1a; 如果你在你项目中需要一些渲染线条的效果&#xff0c;在3D场景中&#xff0c;渲染一个线条出来&#xff08;比如路线图&#xff0c;激光射线&#xff0c;标记点&#xff09;等效果&#xff0c;那这部分的学习一定不要错过喔。 Line Renderer&#xff08;线条…

ECHARTS学习

坐标轴 option {xAxis: {type: category,data: [A, B, C]},yAxis: {type: value},series: [{data: [120, 200, 150],type: line}] }; 1、坐标轴的默认类型type是数值型&#xff0c;而xAxis指定了类目型的data&#xff0c;所以Echarts也能识别出这是类目型的坐标轴&#xff0c;…

第八篇:隔离即力量:Python虚拟环境的终极指南

隔离即力量&#xff1a;Python虚拟环境的终极指南 1 引言 在编程的多元宇宙中&#xff0c;Python语言犹如一颗闪耀的星辰&#xff0c;其魅力不仅仅在于简洁的语法&#xff0c;更在于其庞大而繁荣的生态系统。然而&#xff0c;随着应用的增长和复杂性的提升&#xff0c;开发者们…

手搓带头双向循环链表(C语言)

目录 List.h List.c ListTest.c 测试示例 带头双向循环链表优劣分析 List.h #pragma once#include <stdio.h> #include <stdlib.h> #include <assert.h>typedef int LTDataType;typedef struct ListNode {struct ListNode* prev;struct ListNode* next…

上位机图像处理和嵌入式模块部署(树莓派4b读写json数据)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;ini文件是用来进行配置的&#xff0c;数据库是用来进行数据存储的。那json是用来做什么的呢&#xff0c;json一般是用来做…

[HNOI2003]激光炸弹

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 注意从&#xff08;1,1&#xff09;开始存即可&#xff0c;所以每次输入x,y之后&#xff0c;要x,y。 因为m的范围最大为…

使用Neo4j和Langchain创建知识图谱

使用Neo4j和Langchain创建知识图谱 知识图谱是组织和整合信息的强大工具。通过使用实体作为节点和关系作为边缘&#xff0c;它们提供了一种系统的知识表示方法。这种有条理的表示有利于简化查询、分析和推理&#xff0c;使知识图在搜索引擎、推荐系统、自然语言处理和人工智能…

32.基础乐理-相对音感与绝对音感

相对音感的概念&#xff1a; 就是先给你一个音&#xff0c;告诉你这个音是X&#xff0c;然后再给一个Y音&#xff0c;你就能根据 X 音判断出这个 Y 音是什么&#xff0c;原理是在于你掌握的是 X 与 Y 之间相对距离的感觉&#xff0c;比如图1&#xff0c;弹两个键 先弹 小字一组…

Ubuntu GUI使用Root用户登录指南

Ubuntu GUI使用Root用户登录指南 一、前言 默认情况下&#xff0c;Ubuntu 禁用了 root 账户&#xff0c;我们必须使用 sudo 命令来执行任何需要 root 权限的任务&#xff0c;比如像这样删除一个系统配置文件&#xff08;操作危险&#xff0c;请勿尝试&#xff09;&#xff1a;…

python可视化学习笔记折线图问题-起始点问题

问题描述&#xff1a; 起始点的位置不对 from pyecharts.charts import Line import pyecharts.options as opts # 示例数据 x_data [1,2,3,4,5] y_data [1, 2, 3, 4, 5] # 创建 Line 图表 line Line() line.add_xaxis(x_data) line.add_yaxis("test", y_data) li…

安装“STM32F4 Discovery Board Programming with Embedded Coder”MATLAB获取硬件支持包失败

安装“STM32F4 Discovery Board Programming with Embedded Coder”MATLAB获取硬件支持包失败 -完美解决方法 显示请续订您的软件维护服务&#xff0c;解决办法 根据知乎的文章 MATLAB获取硬件支持包失败&#xff0c;显示请续订您的软件维护服务&#xff0c;解决办法&#xff…

《QT实用小工具·五十》动态增删数据与平滑缩放移动的折线图

1、概述 源码放在文章末尾 该项目实现了带动画、带交互的折线图&#xff0c;包含如下特点&#xff1a; 动态增删数值 自适应显示坐标轴数值 鼠标悬浮显示十字对准线 鼠标靠近点自动贴附 支持直线与平滑曲线效果 自定义点的显示类型与大小 自适应点的数值显示位置 根据指定锚点…

程序包的创建

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

Python-快速搭建一个管理平台

目录 &#x1f4dc; 准备工作 一、项目介绍 ✨ 二、制作数据库表 添加信息 ⚒️ 三、运行client.exe &#x1f680; 1、连接数据库&#xff0c;选择对应表&#xff0c;生成代码 2、把后端代码依次复制到项目中 3、把前端代码依次复制到前端项目中 4、添加路由 四、运行后端项目…

异地组网,让“远程运维”更简单

您是否在联网场景中有过这些需求&#xff1f; 摄像头需要联网统一监控、PLC需要联网告别本地升级、工控机需要联网告别本地配置、广告屏需要联网告别本地下载视频、远程打开终端设备WEB进行配置......这些问题有人新升级的“异地组网”功能统统可以解决&#xff01; 告别繁琐…