MyBatis 參數處理是開發者最易踩坑的領域:Parameter 'xxx' not found、Could not get property、Expression evaluated to null、Return value was not iterable。本文從源碼層面完整追蹤參數從 Mapper 介面方法進入、轉換、封裝、動態 SQL 綁定、SQL 參數映射的全生命週期,建立精確心智模型。
介面方法調用時,MyBatis 先將陣列參數 Object[] args 轉換為統一參數物件:
關鍵規則
| 介面簽名 | params Map 內容 | 轉換結果 |
|---|---|---|
selectUser(String name) | {0: "0"} (無 @Param,編譯無參數名) | 直接返回 name 字串 (單參數特例) |
selectUser(@Param("name") String name) | {0: "name"} | {name=value, param1=value} |
selectUser(String name, int age) | {0: "0", 1: "1"} | {param1=name, param2=age, 0=name, 1=age} |
selectUser(@Param("n") String name, @Param("a") int age) | {0: "n", 1: "a"} | {n=name, a=age, param1=name, param2=age} |
建議:多參數**務必加 @Param**,單參數建議加 @Param 避免編譯選項影響。
若轉換後參數為單一 Collection、List、陣列,MyBatis 會再封裝為 Map:
map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
這就是 <foreach collection="list"> 預設可用 list/array/collection 的原因。**Map 類型不會被封裝**,需自行指定 key。
<if>, <where>, <foreach> 等標籤中的 OGNL 表達式求值,依賴 DynamicContext.bindings (型別 ContextMap)。
// 簡化邏輯 if (boundSql.hasAdditionalParameter(prop)) value = boundSql.getAdditionalParameter(prop); // foreach 動態參數 else if (parameterObject == null) value = null; else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) value = parameterObject; // 簡單型別/單值 else value = metaObject.getValue(prop); // 反射取值 (支援巢狀 user.address.city) typeHandler.setParameter(ps, i+1, value, jdbcType);
特性:型別安全、防注入、支援 TypeHandler 自訂轉換、jdbcType 控制 NULL 值處理。
處理類:TextSqlNode.BindingTokenParser.handleToken
用途:動態表名、欄位名、排序欄位、IN 列表 (需自行拼接)、靜態常數引用
${@com.Const@VAL}。風險:SQL 注入、無型別轉換、不走 PreparedStatement 快取。
五、foreach 深度解析:動態參數生成機制
index, value -> item applyIndex(context, indexKey, index); applyItem(context, itemKey, o); // 遞歸處理 foreach 體內容 }動態參數命名規則:
__frch_{itemName}_{index}。巢狀屬性存取:__frch_user_0.username。六、常見錯誤對應解決方案
| 錯誤訊息 | 根因 | 解決 |
|---|---|---|
| Parameter 'xxx' not found | 動態 SQL/OGNL 找不到 key | 檢查 @Param 名稱、wrapCollection 產生的 key (list/array/collection)、foreach 動態參數名 |
| Could not get property 'xxx' | 反射取 POJO 屬性失敗 | 確認 getter 存在、屬性名拼寫、巢狀路徑正確 |
| Expression evaluated to null | foreach collection 表達式為 null | 傳入非空集合、或用 <if test="list != null"> 包裹 |
| Return value was not iterable | collection 非 Collection/Array/Map | 轉換為 List、或實作 Iterable |
| Map 子元素屬性找不到 | Map 值為 null 導致 key 缺失 | map.put(key, null) 保留 key |
collection="list"。put(key, null)。CriteriaBuilder / SQL Builder 類,減少 XML 動態 SQL 複雜度。