mybatis中#{}與${}區別

mybatis 中使用 sqlMap 進行 sql 查詢時,經常需要動態傳遞參數,例如sql 如下:

select * from student where uid=#{uid} AND student_name='${studentName}'

在動態 SQL 解析階段, #{ } 和 ${ } 會有不同的表現:

#{ } 解析爲一個 JDBC 預編譯語句(prepared statement)的參數標記符佔位符 ?。

上面的例子就被解析爲

...... uid=? 

${ } 僅僅爲一個純碎的 string 替換,在mybatis的動態 SQL 解析階段將會進行變量替換。比如:傳入的studentName參數是“張三”

...... student_name='張三'

最後解析完成的完整語句就是

select * from student where uid= ? AND student_name='張三'

然後把解析好的語句在通過JDBC執行。

 

所以因爲在${ } 在預編譯之前已經被變量替換了,這會存在 sql 注入問題。比如:傳入的studentName參數是“1' OR '1'='1

在mybatis中解析完成後變成了

select * from student where uid= ? AND student_name='1' OR '1'='1'

這樣就查詢出所有的數據了。

源碼查看:

在執行mybtis的查詢是,會執行到PreparedStatementHandler#query,查看生成的PreparedStatement

 

 如果傳入參數傳入的studentName參數是“1' OR '1'='1

 

 所以就出現注入的情況了。

如果我們把${studentName}換成#{studentName},在傳入“1' OR '1'='1”呢

在mybatis解析出的結果

 

 在數據庫的執行日誌中查詢執行的sql語句

 發現我們傳入的“'”被轉義了,

 所以通過預編譯,用佔位符的方式傳值可以把一些特殊的字符進行轉義,這樣可以防止一些sql注入。

其原因就是:採用了JDBC的PreparedStatement,就會將sql語句:“select * from student where uid= ? AND student_name= ?” 預先編譯好,也就是SQL引擎會預先進行語法分析,產生語法樹,生成執行計劃,也就是說,後面你輸入的參數,無論你輸入的是什麼,都不會影響該SQL語句的語法結構了,因爲語法分析已經完成了,而語法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。所以即使你後面輸入了這些sql命令,也不會被當成sql命令來執行了,因爲這些SQL命令的執行,必須先得通過語法分析,生成執行計劃,既然語法分析已經完成,已經預編譯過了,那麼後面輸入的參數,是絕對不可能作爲SQL命令來執行的,只會被當做字符串字面值參數。所以的sql語句預編譯可以防禦SQL注入。而且在多次執行同一個SQL時,能夠提高效率。原因是SQL已編譯好,再次執行時無需再編譯。

默認使用PreparedStatement是不能執行預編譯的,這需要在url中給出useServerPrepStmts=true參數(MySQL Server 4.1之前的版本是不支持預編譯的,而Connector/J在5.0.5以後的版本,默認是沒有開啓預編譯功能的)。
例如:jdbc:mysql://localhost:3306/test?useServerPrepStmts=true
這樣才能保證mysql驅動會先把SQL語句發送給服務器進行預編譯,然後在執行executeQuery()時只是把參數發送給服務器。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章