Mybatis中 #{} 和 ${}的区别
动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。
mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。
经常碰到这样的面试题目:#{}和${}的区别是什么?
网上的答案是:
#{}是预编译处理,$ {}就是简单的字符串替换。
mybatis在处理#{}时,会将sql中的#{}替换为 ? 号,调用PreparedStatement的set方法来赋值;
而mybatis在处理 $ { } 时,就是简单的字符串替换,把 ${ } 替换成变量的值作为sql的一部分。
使用 #{} 可以有效的防止SQL注入,提高系统安全性。
我们来从代码剖析二者的区别:
${param} 传递的参数会被当成sql语句中的一部分(因此易发生sql注入),比如传递表名,字段名
例子:(传入值为id)
order by ${param}
则解析成的sql为:
order by id
#{parm}传入的数据都当成一个字符串,会对自动传入的数据加一个双引号
例子:(传入值为id)
select * from table where name = #{param}
则解析成的sql为:
select * from table where name = "id"
为了安全,能用#的地方就用#方式传参,这样可以有效的防止sql注入攻击
什么是sql注入?
sql注入简介
直接上了百度的例子,感觉一看就清晰明了
某个网站的登录验证的SQL查询代码为:
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
恶意填入
userName = "1' OR '1'='1" 与passWord = "1' OR '1'='1" 时,将导致原本的SQL字符串被填为:
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
也就是实际上运行的SQL命令会变成下面这样的(因为sql会进行优化处理)
strSQL = "SELECT * FROM users;"
这样在后台帐号验证的时候巧妙地绕过了检验,达到无账号密码,亦可登录网站。所以SQL注入攻击被俗称为黑客的填空游戏。
动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。
mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。
在下面的语句中,如果 username 的值为 zhangsan,则两种方式无任何区别:
select * from user where name = #{name};
select * from user where name = ${name};
其解析之后的结果均为
select * from user where name = 'zhangsan';
但是 #{} 和 ${} 在预编译中的处理是不一样的。
#{} 在预处理时,会把参数部分用一个占位符 ? 代替,变成如下的 sql 语句:
select * from user where name = ?;
而 ${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会被解析成
select * from user where name = 'zhangsan';
以上,#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中。
那么,在使用过程中我们应该使用哪种方式呢?
答案是,优先使用 #{}。因为 ${} 会导致 sql 注入的问题。看下面的例子:
对于这个题目我感觉要抓住两点:
(1)$ 符号一般用来当作占位符,常使用Linux脚本的人应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分$和#,从而不容易记错了。
(2)预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
经过以上分析来总结一下:
总结:
可从两方面来答:
-
#{} 和 ${} 在预编译中的处理不一样。
- #{} 在预处理时,会把参数部分用一个占位符 ? 代替。 - 而 ${} 则只是简单的字符串替换。
#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中。
-
${} 传递的参数会被当成sql语句中的一部分,直接是字符串替换(因此易发生sql注入)
#{} 传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
那么问题来了,既然#{}比${}这么有优势为什么还要${}呢?换句话说,就是什么时候用#{},什么时候用${}呢?
什么时候用#{},什么时候用${}呢?
不知道大家有没有想过一个问题?
SELECT * FROM #{tableName}; //有没有想过??
上面我们已经说过了占位符的意义就是作为值的存在,所以如果作为值的话,那么发送的sql语句就是这样的:
SELECT * FROM ? ;
大家都知道,带问号的sql语句是要传递参数的,好!!假如!!我们传入了参数user,那么查询语句就是这样的:
SELECT * FROM 'user' ;
请问,这种sql语句能够执行成功吗??试试就知道了!!
很明显语法错误!!
那么有什么办法能够解决呢??
没错!!!${}能够解决这个问题,${}的功能是直接进行字符串拼接。这也是为什么${}不能够防止一般的sql注入攻击。因为它是拼接啊!!
这样写就行了:
SELECT * FROM ${tableName} ;
//如果传入基本类型如字符串时就要把tableName改为value才能够成功取值。
因为${}是简单的字符串拼接,所以mybatis解析成了:
SELECT * FROM user ;
使用示例:
最后总结下:
-
一般${}用在我们能够确定值的地方,也就是我们程序员自己赋值的地方。
-
而#{}一般用在用户输入值的地方!!