MyBatis中的#{} 和 ${}

embedded/2024/10/19 21:27:22/

目录

#{} 和 ${}

预编译 SQL 和 即时 SQL

SQL注入

${}的使用


#{} 和 ${}的使用

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

使用#{}进行赋值:

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

测试并观察结果:

java">    @Testvoid selectById() {System.out.println(userInfoMapper.selectById(2));}

 使用 ${} 进行赋值

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

测试并观察结果:

java">    @Testvoid selectById2() {System.out.println(userInfoMapper.selectById2(2));}

 对比两次运行的结果:

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

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

java">    @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);

分别进行测试:

java">    @Testvoid selectByUsername() {System.out.println(userInfoMapper.selectByUsername("zhangsan"));}@Testvoid selectByUsername2() {System.out.println(userInfoMapper.selectByUsername2("zhangsan"));}

 观察结果:

我们可以发现:

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

我们可以修改代码:

java">    @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 注入:

当我们传递参数:

java">    @Testvoid selectByUsername2() {System.out.println(userInfoMapper.selectByUsername2("' or 1='1"));}

测试结果:

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

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

UserController:

java">@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:

java">@Service
public class UserService {@Autowiredprivate 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:

java">@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 注入的风险,所以我们尽量使用 #{} 进行查询,

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

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

${}的使用

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

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

测试:

java">    @Testvoid selectAllUserBySort() {userInfoMapper.selectAllUserBySort("desc");}

而当我们使用 #{}

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

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

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

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

测试:

java">    @Testvoid selectAllUserByLike() {userInfoMapper.selectAllUserByLike("zhang");}

而当我们使用 #{} 时:

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

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

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


http://www.ppmy.cn/embedded/28521.html

相关文章

python中的进程间通信

进程间数据是否共享 在Python中&#xff0c;进程之间默认是不共享内存的。每个进程都有自己独立的内存空间&#xff0c;这意味着在一个进程中对数据的修改不会影响到另一个进程中的同名数据。然而&#xff0c;Python提供了几种方式来实现进程间的数据共享&#xff1a; 使用 mu…

IP纯净度对跨境电商有什么直接影响?

IP纯净度对跨境电商具有直接且深远的影响。在跨境电商的运作中&#xff0c;IP地址扮演着至关重要的角色&#xff0c;而IP纯净度则直接关系到跨境电商的网络安全性、访问效果以及业务竞争力。 第一点&#xff0c;纯净的IP地址对于提升跨境电商的网络安全性具有关键作用&#xf…

【JavaEE网络】从数据链路层到应用层的DNS

目录 数据链路层以太网 DNS 数据链路层 越往下与程序员越远 代表协议&#xff1a;以太网。平常用的网线也叫“以太网线”&#xff0c;平常用的交换机也叫“以太网交换机” 以太网 认识以太网 “以太网” 不是一种具体的网络&#xff0c;而是一种技术标准&#xff1b;既包含…

HTML5实用大全(Part.1)

引言&#xff1a; 哈喽&#xff0c;各位小伙伴们&#xff0c;在本篇博客我将带领大家走进前端中的HTML5,利用HTML我们将可以在网页上自我创作内容&#xff0c;现在学起来&#xff0c;不久后自己也能制作一个花哨的项目了呢&#xff0c;那么&#xff0c;我们开始吧&#xff01; …

【navicat】oracle library is not loaded 问题复现和解决方案

问题原因&#xff1a;客户端oci版本安装错误&#xff0c;navicat需要64位的oci,但是使用32位的oci。 解决方案&#xff1a;官网下载64位oci进行配置。本次演示的解决多了splplus&#xff0c;其实不必要安装也能运行。 首先判断是否数据库已经打开 尝试使用splplus连接数据库 1…

Vue指令、生命周期、Axios异步请求方式

Vue 是一款用于构建用户界面的渐进式的JavaScript框架 一、v-for v-for "(item, index) in items" 参数说明&#xff1a; items 遍历的数组 item 遍历出来的元素 index 为索引/下表&#xff0c;从0开始&#xff0c;可以省略&#xff0c;省略index 语法&#xff1a…

6.k8s中的secrets资源

一、Secret secrets资源&#xff0c;类似于configmap资源&#xff0c;只是secrets资源是用来传递重要的信息的&#xff1b; secret资源就是将value的值使用base64编译后传输&#xff0c;当pod引用secret后&#xff0c;k8s会自动将其base64的编码&#xff0c;反编译回正常的字符…

supervisor 简单理解

1. 找到配置文件&#xff0c;/etc/supervisor/supervisor.conf 添加 [include] files /www/server/panel/plugin/supervisor/profile/*.ini test.ini文件内容 [program:rabbitmq-consume] process_name%(program_name)s_%(process_num)02d directory/www/wwwroot command/w…