文章目錄
- 一、概述
- 二、`#{}` 和 `${}` 的核心區別
- 1. 底層機制
- 代碼示例
- 2. 核心區別總結
- 三、為什么表名只能用 `${}`?
- 1. 預編譯機制的限制
- 2. 動態表名的實現
- 四、安全性注意事項
- 1. `${}` 的風險場景
- 2. 安全實踐
- 五、面試高頻考點
- 1. 基礎原理類問題
- **問題 1**:
- **問題 2**:
- 2. 安全與設計類問題
- **問題 3**:
- **問題 4**:
- 3. 擴展實戰類問題
- **問題 5**:
- 六、總結與最佳實踐
- 1. 使用場景對比
- 2. 最佳實踐
- 七、附錄:MyBatis 預編譯原理圖示
一、概述
在 MySQL 和 MyBatis 等框架中,${}
和 #{}
是動態 SQL 中常用的占位符。它們的核心差異在于 預編譯機制 和 安全性,正確使用二者是后端開發的基本功,也是面試中的高頻考點。本文將從原理、場景、安全性及面試題四方面深入解析。
二、#{}
和 ${}
的核心區別
1. 底層機制
占位符 | 預編譯 | 替換方式 | 安全性 |
---|---|---|---|
#{} | ? | 參數化綁定 (? ) | 高 |
${} | ? | 字符串直接拼接 | 低 |
代碼示例
-- #{}
SELECT * FROM users WHERE name = #{name};
-- 預編譯后:SELECT * FROM users WHERE name = ?;-- ${}
SELECT * FROM ${table} WHERE id = 1;
-- 替換后:SELECT * FROM users_2023 WHERE id = 1;
2. 核心區別總結
#{}
:
用于替換 值類型(如 WHERE 條件值、INSERT 字段值),通過PreparedStatement
預編譯,防止 SQL 注入。${}
:
用于替換 標識符(如表名、列名、ORDER BY 子句),直接拼接字符串,需手動校驗安全性。
三、為什么表名只能用 ${}
?
1. 預編譯機制的限制
- 數據庫協議限制:預編譯占位符
?
僅支持替換值類型(字符串、數字等),不能替換表名、列名等標識符。 - 語法合法性:以下寫法直接報錯:
-- 錯誤!表名無法預編譯 SELECT * FROM ? WHERE id = 1;
2. 動態表名的實現
若需根據業務邏輯動態切換表(如分表場景),只能通過字符串拼接:
<!-- MyBatis 示例 -->
<select id="selectLogs" resultType="Log">SELECT * FROM logs_${month}
</select>
四、安全性注意事項
1. ${}
的風險場景
// 惡意輸入導致 SQL 注入
String userInput = "users; DROP TABLE users; --";
String sql = "SELECT * FROM " + userInput;
// 執行結果:SELECT * FROM users; DROP TABLE users; --
2. 安全實踐
- 禁止用戶控制表名:表名應在代碼層生成(如根據時間分表),而非直接傳遞用戶輸入。
- 白名單校驗:若必須動態傳參,需校驗參數格式(如正則匹配
^[a-zA-Z0-9_]+$
)。 - SQL 審計:攔截非常規表名操作(如
information_schema
)。
五、面試高頻考點
1. 基礎原理類問題
問題 1:
“MyBatis 中 #{}
和 ${}
的底層實現有什么區別?”
答:
#{}
使用PreparedStatement
預編譯,參數替換為?
,防止 SQL 注入。${}
直接拼接字符串,無預編譯,需手動處理安全性。
場景:
面試官考察候選人對 MyBatis 執行過程的理解,是否清楚預編譯機制。
問題 2:
“為什么表名必須用 ${}
?能否用 #{}
?”
答:
數據庫協議規定預編譯占位符 ?
只能替換值類型,不能替換表名、列名等標識符。若強行使用 #{}
,最終生成的 SQL 會因語法錯誤無法執行。
場景:
面試中常見于考察 SQL 預編譯機制的底層知識。
2. 安全與設計類問題
問題 3:
“如何安全地使用 ${}
動態指定表名?”
答:
- 代碼層控制:表名由系統生成(如
user_2023
),而非用戶傳入。 - 白名單校驗:若需外部傳入,校驗參數是否符合命名規范(如正則匹配)。
- 日志監控:記錄所有動態表名操作,便于審計。
場景:
考察安全意識和實際工程經驗,常見于金融、數據安全相關崗位。
問題 4:
“除了表名,還有哪些場景必須用 ${}
?”
答:
- 動態列名:
SELECT ${column} FROM table
- 排序字段:
ORDER BY ${sortField}
- 動態 SQL 片段:
<if test="condition">${sqlSegment}</if>
場景:
面試官可能延伸考察動態 SQL 的靈活應用能力。
3. 擴展實戰類問題
問題 5:
“如果必須讓用戶傳入表名,如何設計安全方案?”
答:
- 前端傳遞表名編碼(如
1=users, 2=products
),后端映射為真實表名。 - 參數加密:用戶傳入加密參數,后端解密后匹配預定義表名。
- 數據庫權限隔離:動態表操作使用只讀低權限賬號。
場景:
高級崗位考察系統設計能力,尤其是安全與靈活性平衡的方案。
六、總結與最佳實踐
1. 使用場景對比
場景 | 占位符 | 示例 |
---|---|---|
WHERE 條件值 | #{} | WHERE id = #{id} |
動態表名/列名 | ${} | SELECT * FROM ${table} |
排序字段 | ${} | ORDER BY ${sort} |
2. 最佳實踐
- 默認使用
#{}
:除非必須使用${}
。 - 最小化
${}
暴露:禁止用戶傳入未經驗證的參數。 - 日志 + 監控:記錄所有動態 SQL 操作,及時預警異常行為。
七、附錄:MyBatis 預編譯原理圖示
MyBatis 執行流程:
1. 解析 XML SQL → 2. 替換 `#{}` 為 `?` → 3. 預編譯 SQL → 4. 綁定參數值 → 5. 執行
文檔說明:本文適用于初中級后端開發者鞏固知識點,以及資深開發者面試復習。建議結合 MyBatis 源碼和 MySQL 協議文檔深入理解。