当前位置 博文首页 > JustinQin:MyBatis-- 浅谈SQL中 #、$参数的动态解析过程

    JustinQin:MyBatis-- 浅谈SQL中 #、$参数的动态解析过程

    作者:[db:作者] 时间:2021-09-12 09:06

    目录

    一、学习背景

    二、语句过程(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动态解析,#{}、${}解析的不同展开学习,相信一定会给你的回答再加点猛料,没时间看过程的童鞋直接拉最后看总结? ~哇~ 不过这个学习过程也很重要的,有时间就看看吧。

    文章主要阐述个人对所学知识的观点,有不合理的地方,欢迎纠正补充,有大犇光顾,欢迎给点指引,蟹蟹,哈哈哈哈!!!

    二、语句过程(Mybatis)

    三、过程分析

    3.1 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动态解析阶段进行变量替换。

    3.2?sql 预编译

    数据库驱动在发送sql语句和参数给DBMS之前对sql语句进行编译,这样DBMS执行sql时,就不需要重新编译。预编译阶段,会将sql动态解析阶段#{}解析到的占位符?替换为参数值(带引号的)即变量替换。

    3.3 DBMS(执行)

    DBMS即Database Management System(数据库管理系统)。mapper.xml自定义的sql语句,经过动态解析阶段、预编译阶段后,得到最终要发送到DBMS执行的sql。

    四、sql字符串拼接

    动态sql语句进行字符串拼接时写法不同,例如模糊查询like后的字符串拼接

    4.1 ${}写法

    select * from table_name where name like '%${name}%';

    4.2 #{}写法

    select * from table_name where name like CONCAT(CONCAT('%', #{name}), '%');

    五、引号问题

    开头提到的面试回答#{}参数带引号,而${}参数不带引号,我觉得这个问题很多博客文章都提到了,但是说的有点抽象了,重新说明下。

    5.1 #{}带引号

    sql动态解析阶段,#{}首先解析为占位符?,预编译阶段,占位符?替换为带引号参数值,?替换为'参数值'

    例:

    select * from table_name where id = #{id};

    sql动态解析阶段(占位符

    select * from table_name where id = ?;

    预编译阶段(变量替换

    select * from table_name where id = '1';

    5.2 ${}不带引号

    sql动态解析阶段,${}则直接替换为参数值,不会加引号。

    select * from table_name where id = ${id};

    sql动态解析阶段(变量替换

    select * from table_name where id = 1;

    预编译阶段(保持不变

    select * from table_name where id = 1;

    六、sql注入问题

    sql注入问题引发的主要原因是参数当中包含了delete、drop、--等字符串,编译得到恶意的sql语句,然后送到DB执行,造成数据库数据安全方面的问题。

    Mybatis的mapper自定义sql时,使用${}就容易引发sql注入问题,因为sql动态解析阶段,${}是直接解析为不带引号的参数值,直接拼接在自定义的sql上,这样很容易得到一个恶意的sql语句。而使用#{}则是先解析为占位符?,然后占位符?替换为带引号的参数值,不会引发sql注入问题。

    所以大多情况下都会使用#{},但特定情况下必须使用${},例如表名、字段名、order by后面就必须要用${},不过需要解决防注入问题,如配置防SQL注入过滤器等。

    实例1:字段名注入

    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的记录。

    实例2:表名注入

    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