在 MyBatis 中,${}
和 #{}
是兩種處理 SQL 參數的占位符,它們在實現機制、安全性、使用場景上存在顯著差異。以下是詳細對比:
核心區別對比
特性 | #{} | ${} |
---|---|---|
底層機制 | 預編譯占位符(PreparedStatement ) | 字符串直接替換 |
安全性 | ? 防止 SQL 注入 | ? 存在 SQL 注入風險 |
參數處理 | 自動添加引號(字符串/日期類型) | 需手動添加引號(否則語法錯誤) |
性能 | 較低(預編譯開銷) | 較高(直接拼接 SQL) |
適用場景 | 動態參數值(如 WHERE id = ? ) | 動態 SQL 片段(如表名、排序字段) |
機制詳解
-
#{}
(預編譯占位符)-
MyBatis 會將其解析為 JDBC 的?占位符,通過PreparedStatement預編譯 SQL。
-
參數值會被安全轉義,例如:
SELECT * FROM users WHERE name = #{name} -- 轉換為:SELECT * FROM users WHERE name = ?
若name = "John",實際執行時參數值會被安全綁定為'John'。
-
-
${}
(字符串替換)-
直接替換為參數值的字面量,無預編譯或轉義。
-
例如:
SELECT * FROM users WHERE id = ${id} -- 若 id=1,替換為:SELECT * FROM users WHERE id = 1
若id = "1 OR 1=1",則 SQL 會變為:
SELECT * FROM users WHERE id = 1 OR 1=1 -- 查詢所有數據,存在 SQL 注入風險[1,5](@ref)
-
安全性問題
-
#{}
:天然防 SQL 注入,適用于用戶輸入或外部參數。 -
${}
:高風險!僅適用于完全可控的靜態值(如內部生成的表名、列名)。錯誤示例(模糊查詢):
SELECT * FROM products WHERE name LIKE '%${keyword}%' -- 若 keyword="' OR 1=1 --",導致數據泄露!
使用場景對比
? #{}
的適用場景
-
普通條件查詢(值動態傳遞):
SELECT * FROM orders WHERE user_id = #{userId} [1,4](@ref)
-
日期/字符串參數(自動添加引號):
INSERT INTO logs (content) VALUES (#{logContent}) -- 自動轉為 'xxx' [7,8](@ref)
-
模糊查詢(安全寫法):
SELECT * FROM products WHERE name LIKE CONCAT('%', #{keyword}, '%') [2,3](@ref)
?? ${}
的適用場景
-
動態表名/列名(SQL 片段不可預編譯):
SELECT * FROM ${tableName} WHERE ${column} = 1 [3,5,7](@ref)
-
排序字段(如ORDER BY ${sortField}):
SELECT * FROM users ORDER BY ${orderBy} DESC [3,7](@ref)
-
批量操作(如IN子句):
DELETE FROM cart WHERE id IN (${ids}) -- ids="1,2,3" [7](@ref)
最佳實踐與避坑指南
-
默認使用
#{}
:除非必須動態拼接 SQL 片段,否則一律用#{}確保安全。 -
${}
的防御措施:-
僅允許傳入白名單值(如預定義表名列表)。
-
手動過濾危險字符(如空格、分號)。
-
-
模糊查詢的替代方案:
-
用CONCAT函數(推薦):
SELECT * FROM table WHERE name LIKE CONCAT('%', #{text}, '%')
-
程序層拼接(Java 中生成"%text%"再傳入)。
-
💎 總結
-
#{}
= 安全優先:處理動態值(用戶輸入、條件參數),預編譯防注入。 -
${}
= 謹慎使用:處理動態 SQL 片段(表名、排序),需嚴格校驗輸入。
關鍵口訣:**“值用井號(
#
),結構用刀($
)”——值動態用#{}
,SQL 結構動態用${}
。實際開發中,95% 的場景應使用#{},僅在必要時(如分表)謹慎使用${}。