在 MyBatis 中,#{} 和 ${} 是兩種截然不同的參數替換機制,正確理解它們的區別對於編寫安全、高效的 SQL 至關重要。
?// Mapper XML
select * from user where name = #{name}
// 傳入參數:name = "張三"
// 實際執行 SQL:
select * from user where name = '張三'
// 傳入參數包含單引號:name = "O'Reilly"
// 實際執行 SQL(自動轉義):
select * from user where name = 'O\'Reilly'
// Mapper XML
select * from ${tableName} order by ${columnName} ${sortOrder}
// 傳入參數:tableName="user", columnName="create_time", sortOrder="DESC"
// 實際執行 SQL:
select * from user order by create_time DESC
這是最常見的必須使用 ${} 的場景:
// 錯誤寫法(使用 #{})
select * from user order by #{columnName}
// 實際 SQL:select * from user order by 'create_time' -- 無效,視為字符串常量
// 正確寫法(使用 ${})
select * from user order by ${columnName}
// 實際 SQL:select * from user order by create_time -- 有效,視為列名
早期 iBATIS 使用 #property# 和 $property$ 語法,MyBatis 3.x 改為 #{} 和 ${},但核心原理未變:
| 特性 | # / #{} | $ / ${} |
|---|---|---|
| 參數處理 | 視為字符串,自動加引號 | 直接顯示生成在 SQL 中 |
| 輸入 111 | 解析為 "111" | 解析為 111 |
| 輸入 id | 解析為 "id" | 解析為 id |
| SQL 注入防護 | 強 | 無 |
| 主要用途 | 數據值參數 | 數據庫對象、SQL 片段 |
#{}${},務必在業務層對參數進行白名單驗證,杜絕用戶輸入直接拼接${} 使用場景是否合理、是否有防注入措施「接受用戶輸入內容並直接提供給語句中不變的字符串是不安全的,會導致潛在的 SQL 注入攻擊。因此你不應該允許用戶輸入這些字段,或者自行轉義並驗證。」
#{} 是參數化查詢的標準方式,提供安全性與性能;${} 是字符串模板替換,僅用於 SQL 結構動態構建。記住口訣:「能用 # 就不用 $,用 $ 必驗證」。