面试题:MyBatis中 #{} 跟 ${} 的区别
前置知识
SQL注入:后端并未对前端传来的参数进行严格的校验,同时后端存在将前端传来的参数使用字符串或者直接将参数拼接到SQL语句中,导致后端的SQL语句被篡改并且能成功执行
四大SQL注入原理
- 恶意拼接查询--查询、插入、更新和删除数据,且使用分号来分隔不同的命令
- 原SQL语句
select * from student where id = ${id}
- 恶意拼接后的SQL
select * from student where id = '1'; delete from student;
多出了一条删除语句,将删除全表数据- 使用注释方式执行非法命令--在MySQL中,注释的符号为
--
- 原SQL语句
select * from student where age = ${age} and sex = '1'
- 非法命令注入的SQL
select * from student where age = 11 or 22 and sleep(500) -- and sex = '1'
注释将后面的查询条件去掉了,同时SLEEP(500) 将导致 SQL 语句一直运行,耗尽系统资源- 接收非法的参数--SQL中传入的字符串参数是用单引号引起来的,如果字符串本身包含单引号而没有被处理,那么可能会篡改原本 SQL 语句的作用
- 原SQL语句
select * from student where name = ${name} and sex = '1'
- 接收非法参数的SQL:
select * from student where name = 'wen'' and sex = '1'
此语句数据后台会执行报错- 添加额外的条件--SQL中添加额外条件,以此来改变执行行为。条件一般为真值表达式
- 原SQL语句:
select * from student where name = ${name}
- 添加额外条件的SQL:
select * from student where name = 'wen' or 1=1
此语句将查询所有数据而非name为wen的数据
SQL预编译语句:某一条SQL能被反复执行,或者每次执行时只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同),此时可以将此类SQL语句中的值用占位符替代
SQL预编译优势
- 一次编译、多次运行,省去了词法语义解析、语句优化、制定执行计划等过程
- 防止 SQL 注入
MyBatis中 #{} 跟 ${} 的区别
在MyBatis的Mapper映射文件中,有两种动态的引用形参变量的方式进行取值:#{} 和 ${}
<select id="getAllStudent" parameterType="string" resultType="com.wen.server.pojo.Student"> SELECT * FROM student WHERE name like concat('%',concat(#{name},'%')) </select>
<select id="getAllStudent" parameterType="string" resultType="com.wen.server.pojo.Student"> SELECT * FROM student WHERE name like concat('%',concat('${name}','%')) </select>
#{}方式
使用 #{}
可实现预编译功能,SQL中的参数解析为JDBC的预编译语句中参数标记符,将参数部分使用占位符 ?
代替,同时对传入的参数值经PreparedStatement方法的强制类型检查和安全检查操作,最后作为一个合法的字符串传入
数据类型检查:若检测到为数值类型,就不加引号,即 ? ;若检测到位字符串类型,就加上引号,即 '?'
安全检查:若变量的值带有引号,会对引号进行转义处理,可以防止SQL注入
预编译步骤如下
- 将参数部分使用占位符
?
代替
SELECT * FROM student WHERE name like concat('%',concat(?,'%'))
- 强制类型检查和安全检查操作,最后作为一个合法的字符串传入
- 预编译参数传入
SELECT * FROM student WHERE name like concat('%',concat('wen','%'))
- SQL执行
SELECT * FROM student WHERE name like '%wen%'
使用场景:动态拼接SQL,多数用在模糊查询、where子句中多个条件的查询、pojo对象等。例如 SELECT * FROM student WHERE name like concat('%',concat(#{name},'%'))
${}方式
使用 ${}
方式是做字符串的替换,在动态SQL解析阶段会将变量直接替换为参数的值。使用该语法会存在SQL注入问题
预编译步骤如下
- 在SQL解析阶段就将该变量的替换成了变量的值
SELECT * FROM student WHERE name like concat('%',concat('wen','%'))
- SQL执行
SELECT * FROM student WHERE name like '%wen%'
使用场景:需要动态指定查询字段名、表名、order by指定排序字段名的动态SQL,并且需要注意什么时候需要带单引号,什么时候不需要(表名、字段名、数值不需要单引号)。例如 SELECT ${columnName} FROM ${tableName} WHERE name like concat('%',concat(#{name},'%'))