情況一:只用 #{}
<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select * from USER where userName=#{userName} and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select * from USER where userName=? and userPassword =?; ==> Parameters: mww(String), 123(String) <== Total: 1
結論:
#{}會在預編譯期,生成兩個 ?,作爲佔位符。
情況二:一個用 ${} 一個用 #{}
<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select * from USER where userName=${userName} and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select * from USER where userName=mww and userPassword =?; ==> Parameters: 123(String)
結論:很顯然 ${} 是直接拼成字符串的 ,#{} 是生成 ?佔位符的。
而且 因爲 userName:mww 是字符串,所以 這種寫法顯然也是錯誤的。
會報出如下錯誤:
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'mww' in 'where clause'
所以正確的寫法是這樣的:爲字符串字段加上單引號 ' '
<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select * from USER where userName='${userName}' and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select * from USER where userName='mww' and userPassword =?; ==> Parameters: 123(String) <== Total: 1
結論:顯然這種寫法是正確的,從這裏可以看出,預編譯期 ${} 是直接把參數拼結到SQL中,
運行時,就只傳入了一個 #{} 修飾的參數。
${}的這種寫法還有一個安全隱患,那就是 SQL注入。
情況三: ${} SQL注入:
<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select * from USER where userName='${userName}' and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select * from USER where userName='' OR 1=1 OR '' and userPassword =?; ==> Parameters: 65787682342367(String) <== Total: 2
結論:只要我們在 ${} 輸入 ' OR 1=1 OR ' 無論後面的密碼輸入什麼都可以,查詢到數據,這種情況就是SQL注入。
情況四:#{} 防止SQL注入
<select id="getUserByNameAndPsw" parameterType="map" resultType="com.hotel3.model.User"> select * from USER where userName=#{userName} and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select * from USER where userName=? and userPassword =?; ==> Parameters: ' OR 1=1 OR '(String), 123(String) <== Total: 0
結論:上面預編譯SQL的參數已經由佔位符 { ?} 代替,所以傳入的 ' OR 1=1 OR ' 只會作爲 userName字段的值,
而不會拼入執行的SQL。這樣就達到了防止SQL注入的目的。
在這種用法中, #{} 顯然比 ${} 用法更好。
那 ${} 爲什麼經常在 Mybatis 使用那?
${}正確用法(個人見解):
1、同時傳入一個字段名和字段值:
User u=userService.getUserByNameAndPsw("userName,userType,userPassword",userName,userPassword);
SQL: select ${arg0} from USER
<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select ${arg0} from USER where userName=#{userName} and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?; ==> Parameters: mww(String), 123(String) <== Total: 1
結論:
生成了我們想要SQL語句 :select userName,userType,userPassword from USER。。。。
2、傳入兩個字段名和字段值:
User u=userService.getUserByNameAndPsw("userName,userType,userPassword",userName,userName,userPassword);
SQL: select ${arg0} from USER where ${arg1}=#{userName}
<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User"> select ${arg0} from USER where ${arg1}=#{userName} and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?; ==> Parameters: mww(String), 123(String) <== Total: 1
結論:
按照傳參的順序匹配 SQL 中的 ${arg0},${arg1}。。。
生成我們想要的代碼,但這個方式會使Mybatis 的 Mapper 文件可讀性變差,
如果不看其他的代碼,很難辨別 ${arg0} ,${arg1} 代表的含義。
3、使用Map傳值,提高 Mapper 文件的可讀性
Map map =new HashMap(); map.put("selectValues","userName,userType,userPassword"); map.put("userNamefieId","userName"); map.put("userName",userName); map.put("userPassword",userPassword); User u=userService.getUserByNameAndPsw(map);
Mapper 文件的 xml
<select id="getUserByNameAndPsw" parameterType="map" resultType="com.hotel3.model.User"> select ${selectValues} from USER where ${userNamefieId}=#{userName} and userPassword =#{userPassword}; </select>
結果:
==> Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?; ==> Parameters: mww(String), 123(String) <== Total: 1
結論:
結果和arg0、arg1的傳值方式相同,但 Mapper 文件的 xml 中的SQL語句可以
通過對 map 中 key 值命名提高可讀性。
注:以上SQL,均爲預編期生成的SQL。
注2:${} 每次傳值不同 SQL 語句不同,可以靈活生成 SQL,
但每次都需要進行預編譯,對效率會有影響,至於要不要使用 ${}還要具體情況具體分析。