#{} 是參數佔位符(預編譯),${} 是字符串拼接(直接替換)。前者防 SQL 注入,後者用於動態表名/列名/排序字段等無法預編譯的場景。
| 特性 | #{param} | ${param} |
|---|---|---|
| 處理機制 | JDBC 預編譯 PreparedStatement,參數化查詢 | SQL 解析前直接進行字符串替換 |
| SQL 注入防護 | ✅ 完全防護,參數視為純數據 | ❌ 無防護,輸入會成為 SQL 一部分 |
| 適用場景 | WHERE 條件值、INSERT/UPDATE 值、IN 列表值等 | 動態表名、列名、ORDER BY 字段、數據庫函數名等 |
| 類型處理 | 自動處理類型轉換、引號轉義 | 需手動確保類型匹配、加引號(字符串) |
| 性能 | 可利用數據庫執行計劃緩存 | 無法複用執行計劃,頻繁變化會增加解析開銷 |
<select id="selectUser" resultType="User">
SELECT * FROM users WHERE username = #{username} AND status = #{status}
</select>
<!-- 對應 Java: selectUser("admin', '1=1--", 1) 生成 SQL -->
<!-- SELECT * FROM users WHERE username = ? AND status = ? -->
<!-- 參數: ["admin', '1=1--", 1] 安全! -->
<select id="selectByPage" resultType="User">
SELECT * FROM ${tableName}
WHERE status = #{status}
ORDER BY ${orderColumn} ${orderDirection}
LIMIT #{offset}, #{pageSize}
</select>
<!-- 調用: selectByPage({tableName:"users", orderColumn:"create_time", orderDirection:"DESC", status:1, offset:0, pageSize:10}) -->
<!-- 生成: SELECT * FROM users WHERE status = ? ORDER BY create_time DESC LIMIT ?, ? -->
${} 參數進行嚴格白名單過濾。
public List<User> selectByPage(QueryParam param) {
// 白名單校驗
Set<String> allowedColumns = Set.of("id", "username", "create_time", "update_time");
if (!allowedColumns.contains(param.getOrderColumn())) {
throw new IllegalArgumentException("非法排序字段");
}
// 方向校驗
if (!"ASC".equalsIgnoreCase(param.getOrderDirection()) && !"DESC".equalsIgnoreCase(param.getOrderDirection())) {
param.setOrderDirection("ASC");
}
return mapper.selectByPage(param);
}
${} 替換後的完整 SQL 會打印,注意敏感數據洩露風險。MyBatis 提供 <foreach> 標籤配合 #{} 完美解決 IN 列表問題:
<select id="selectByIds" resultType="User">
SELECT * FROM users WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
#{},覆蓋 95% 以上場景。${},並嚴格實施白名單校驗。${} 使用點的安全性。