MyBatis 作為一款優秀的持久層框架,極大地簡化了 JDBC 代碼與參數手動設置的繁瑣工作。在實際開發中,如何靈活且安全地傳遞多個參數至 SQL 語句,是每位開發者必須掌握的核心技能。本文將深入解析 #{ } 與 ${ } 兩種參數佔位符的本質差異,並通過實例演示動態排序、多條件查詢等常見場景的最佳實踐。
#{ } 採用預編譯機制,MyBatis 會將其解析為 JDBC 的參數佔位符 ?,由數據庫驅動進行類型安全的參數綁定。這能有效防止 SQL 注入,並自動處理字符串轉義、日期格式等類型轉換。
// XML 映射示例
SELECT * FROM users WHERE username = #{username} AND status = #{status}
若傳入 username = "admin' OR '1'='1",最終執行的 SQL 為:
SELECT * FROM users WHERE username = ? AND status = ?
-- 參數綁定:["admin' OR '1'='1", 1]
數據庫會將整個字符串視為用戶名查詢,而非執行惡意邏輯。
${ } 進行簡單的字符串拼接,將變量值直接嵌入 SQL 文本。這雖允許動態指定表名、列名或排序字段,但極易引發 SQL 注入漏洞,嚴禁用於用戶輸入的業務參數。
// 僅適用於可信的動態列名/表名
SELECT * FROM ${tableName} ORDER BY ${columnName} ${order}
需求:前端傳遞排序字段 sortField 與排序方向 sortOrder,實現通用列表排序。
<select id="queryList" resultType="map">
SELECT * FROM t_order
<where>
<if test="status != null"> status = #{status} </if>
</where>
<if test="sortField != null and sortOrder != null">
ORDER BY ${sortField} ${sortOrder}
</if>
</select>
安全加固建議:在 Service 層對 sortField 進行白名單校驗,僅允許預期的列名(如 create_time, amount),杜絕注入風險。
public List<Order> findOrders(@Param("userId") Long userId, @Param("status") Integer status);
XML 中直接使用 #{userId}, #{status}。
Map<String, Object> params = new HashMap<>();
params.put("userId", 1001);
params.put("statusList", Arrays.asList(1,2,3));
params.put("startDate", "2023-01-01");
public class OrderQueryDTO {
private Long userId;
private List<Integer> statusList;
private String startDate;
private String sortField;
private String sortOrder;
// getter/setter
}
#{ },享受預編譯帶來的安全與性能。${ } 時,務必在 Java 端實施嚴格白名單校驗。<where>, <trim>, <foreach> 等動態標籤,構建清晰可維護的映射文件。掌握參數傳遞的本質與邊界,是編寫高質量 MyBatis 代碼的基石。願本文能助你避開坑洼,寫出更安全、優雅的持久層代碼。