当前位置 博文首页 > JustinQin:MyBatis-- 浅谈SQL中 #、$参数的动态解析过程
目录
一、学习背景
二、语句过程(Mybatis)
三、过程分析
3.1 sql动态解析
3.2?sql 预编译
3.3 DBMS(执行)
四、sql字符串拼接
4.1 ${}写法
4.2 #{}写法
五、引号问题
5.1 #{}带引号
5.2 ${}不带引号
六、sql注入问题
实例1:字段名注入
实例2:表名注入
七、总结
学习资料
引号说明
很多Java面试面试官都会问到Mybatis相关问题,其中#{}、${}的使用区别最为常见,大多人都会回答:
#{}参数带引号,而${}参数不带引号;
#{}防sql注入,${}不防sql注入。
单单两句话概括的话,个人觉得这个回答稍微有点肤浅了。具体怎么个带引号法,为什么要防sql注入,注入生效会导致什么结果也没大概说一下。
本文就从Mybatis特性之一的sql动态解析,#{}、${}解析的不同展开学习,相信一定会给你的回答再加点猛料,没时间看过程的童鞋直接拉最后看总结? ~哇~ 不过这个学习过程也很重要的,有时间就看看吧。
文章主要阐述个人对所学知识的观点,有不合理的地方,欢迎纠正补充,有大犇光顾,欢迎给点指引,蟹蟹,哈哈哈哈!!!
sql动态解析是Mybatis的重要特性之一,也是Mybatis相比其他ORM框架的优势所在。sql动态解析时,sql会被解析为BoundSql对象,此处进行动态 SQL的处理。
sql动态解析阶段,#{}、${}有不同的处理体现
#{}解析为占位符?即JDBC 预编译语句(prepared statement)的参数标记符。
select * from table_name where id= #{id};
id传参数值为字符串1,解析为:
select * from table_name where id= ?;
${}则是直接拼接不带引号的参数值到sql语句上。
select * from table_name where id=${id};
解析为:
select * from table_name where id= 1;
表明${}在sql动态解析阶段进行变量替换。
数据库驱动在发送sql语句和参数给DBMS之前对sql语句进行编译,这样DBMS执行sql时,就不需要重新编译。预编译阶段,会将sql动态解析阶段#{}解析到的占位符?替换为参数值(带引号的)即变量替换。
DBMS即Database Management System(数据库管理系统)。mapper.xml自定义的sql语句,经过动态解析阶段、预编译阶段后,得到最终要发送到DBMS执行的sql。
动态sql语句进行字符串拼接时写法不同,例如模糊查询like后的字符串拼接
select * from table_name where name like '%${name}%';
select * from table_name where name like CONCAT(CONCAT('%', #{name}), '%');
开头提到的面试回答#{}参数带引号,而${}参数不带引号,我觉得这个问题很多博客文章都提到了,但是说的有点抽象了,重新说明下。
sql动态解析阶段,#{}首先解析为占位符?,预编译阶段,占位符?替换为带引号参数值,?替换为'参数值'。
例:
select * from table_name where id = #{id};
sql动态解析阶段(占位符)
select * from table_name where id = ?;
预编译阶段(变量替换)
select * from table_name where id = '1';
sql动态解析阶段,${}则直接替换为参数值,不会加引号。
select * from table_name where id = ${id};
sql动态解析阶段(变量替换)
select * from table_name where id = 1;
预编译阶段(保持不变)
select * from table_name where id = 1;
sql注入问题引发的主要原因是参数当中包含了delete、drop、--等字符串,编译得到恶意的sql语句,然后送到DB执行,造成数据库数据安全方面的问题。
Mybatis的mapper自定义sql时,使用${}就容易引发sql注入问题,因为sql动态解析阶段,${}是直接解析为不带引号的参数值,直接拼接在自定义的sql上,这样很容易得到一个恶意的sql语句。而使用#{}则是先解析为占位符?,然后占位符?替换为带引号的参数值,不会引发sql注入问题。
所以大多情况下都会使用#{},但特定情况下必须使用${},例如表名、字段名、order by后面就必须要用${},不过需要解决防注入问题,如配置防SQL注入过滤器等。
delete from table_name where id = ${id};
id传参数值1
delete from table_name where id = 1;
id传参数值1 or id <> x
delete from table_name where id = 1 or id <> x;
结果:注入生效,预计只想删除id=1的记录,却删除了id=1 or id != x的记录。
select * from ${tableName} where id = #{id};
参数 tableName、id传值分别为table_name ; delete table_name; -- 、1
select * from table_name ; delete from table_name ; -- where id= 1;
结果:注入生效,先查询了table_name表,再delete清空表数据,--后面则被注释掉。
1、mybati中sql语句大致执行过程sql动态解析—>sql预编译—>DB执行sql;
2、#{}先动态解析为占位符?,预编译时占位符?替换为带引号的参数值;
3、${}动态解析直接替换为不带引号的参数值;
4、字符串拼接写法不同,例如模糊查询like后面,${}直接拼接到引号里,#{}用CONCAT函数拼接。
5、表名、字段名、order by等特殊情况下必须使用${},但是容易引发sql注入问题,例如参数值带有delete、drop、--等字符串,容易生成恶意SQL语句送到DB执行,导致sql注入生效。因此需要手动防注入,可考虑配置防sql注入过滤器等解决方案。
6.基于性能和安全方面考虑能用#{}的地方都用#{},因为预编译后的sql可以重复使用,且一定程度上可以防sql注入问题。
>>>MyBatis $和#的区别
https://www.cnblogs.com/qianf/p/9534126.html
https://www.cnblogs.com/kangyun/p/5881531.html
https://www.cnblogs.com/linjiaxin/p/6101609.html
>>>防注入
https://www.cnblogs.com/lwdmaib/p/9347999.html
https://blog.csdn.net/JustinQin/article/details/78563497
https://www.cnblogs.com/zouqin/p/5314827.html
SQL字符串连接函数CONCAThttps://www.cnblogs.com/gyrgyr/p/6001244.html
Mybatis源码BoundSqlhttps://blog.csdn.net/lqzkcx3/article/details/78370497
很多博客文章,对于${}的举例说明,经常在字符串拼接时,加上了引号,容易引起误解,而实际上是没有引号的。
例如:
mapper.xml自定义sql为
select * from ${tableName}?where name = ${name} order by ${columnName};
tableName、name、columnName分别传值字符串table_name、Jack、id
sql动态解析阶段
解析为?select * from table_name where name = Jack order by id;
而不是(sql解析报错)?select * from 'table_name' where name = 'Jack' order by 'id';
表明sql动态解析阶段,${}解析为字符串直接拼接,不会加引号,因此${}一般只适用于表名、字段名拼接,但是需要注意sql注入。
cs