目录
前言
预编译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() 来解决。
以上就是本篇所有内容~
若有不足,欢迎指正~