| 特性 | #{param} | ${param} |
|---|---|---|
| 處理方式 | 預編譯,參數化佔位符 | 字符串直接替換 |
| 類型轉換 | 自動類型匹配與轉換 | 無類型處理,原樣輸出 |
| SQL 注入防護 | 強力防護 | 無防護,極高風險 |
| 適用場景 | 值參數傳遞(WHERE 條件、INSERT 值等) | 數據庫對象名(表名、列名、排序字段) |
<select id="getUser" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
MyBatis 將其轉換為 JDBC PreparedStatement:
SELECT * FROM users WHERE id = ?
執行時綁定參數,數據庫引擎預編譯 SQL 結構,參數作為數據傳入,徹底阻斷注入攻擊。若 id 為字符串類型,自動添加引號 'value';數值類型直接傳值。
<select id="getTableData" resultType="Map">
SELECT * FROM ${tableName} WHERE status = 'active'
</select>
MyBatis 直接將變量值拼接進 SQL 字符串:
SELECT * FROM user_table WHERE status = 'active'
警告:若 tableName 來自用戶輸入,攻擊者可構造 user_table; DROP TABLE users; -- 導致災難性後果。
所有值參數(ID、名稱、日期、枚舉值等)統一用 #{},除非有明確不可替代理由。
ORDER BY ${sortColumn}SELECT * FROM user_${shard}WHERE ${dynamicCondition}(需嚴格白名單校驗)// Mapper 接口
List<User> query(@Param("sort") String sortColumn);
// Service 層校驗
private static final Set<String> ALLOWED_COLUMNS = Set.of("id", "name", "created_at");
public List<User> safeQuery(String sort) {
if (!ALLOWED_COLUMNS.contains(sort)) {
throw new IllegalArgumentException("非法排序字段");
}
return mapper.query(sort);
}
MyBatis 啟用預編譯後,SQL 模板發送給數據庫編譯生成執行計劃,參數佔位符 ? 標記數據位置。運行時僅傳遞參數值,數據庫引擎區分「代碼」與「數據」,使注入代碼無法被解析為 SQL 指令。
鐵律:能用
#{}絕不使用${}。必須用${}時,嚴格白名單校驗輸入,並在代碼審查中重點關注。
掌握二者區別是 MyBatis 安全開發的基礎,亦是面試高頻考點。養成代碼審查習慣,從源頭杜絕 SQL 注入隱患。