MyBatis 標準模式要求預定義 SQL 與映射,但實際場景中常需動態構建查詢(如報表引擎、動態表單)或高性能批量寫入。本文深度解析 ${} 直接注入 SQL、ResultMap 靈活映射、<foreach> 批量插入三大核心技術,並給出生產級最佳實踐。
| 特性 | #{} (預編譯參數) | ${} (字符串直接替換) |
|---|---|---|
| 機制 | PreparedStatement 參數綁定 (?) | SQL 字符串拼接,直接替換 |
| 類型處理 | 自動類型轉換、加引號、防注入 | 無任何處理,原樣輸出 |
| 適用場景 | WHERE 條件值、INSERT/UPDATE 值 | 表名、列名、ORDER BY 欄位、完整 SQL 片段 |
| 安全性 | 安全,防 SQL 注入 | 極高風險,必須嚴格校驗輸入 |
<!-- Mapper.xml -->
<select id="queryByDynamicSql" resultType="com.example.model.InstanceModel">
${paramSql}
</select>
// Mapper Interface
List<InstanceModel> queryByDynamicSql(@Param("paramSql") String sql);
調用規範:傳入 SQL 必須包含別名匹配 ResultMap/resultType 屬性:
SELECT inst_id AS instanceId, inst_name AS instanceName FROM instance_table WHERE status = 1
安全防護:
PreparedStatement 參數化處理 WHERE 值,僅用 ${} 拼接結構性片段。resultType:簡單查詢,列名與屬性名一致(或駝峰映射開啟 mapUnderscoreToCamelCase=true)。resultMap:複雜映射(列名不一致、嵌套對象、多結果集、構造器注入),定義一次,多處複用。對比兩種寫法:
-- 低效:多條 INSERT(多次網路往返、多次解析優化)
INSERT INTO student (name, age) VALUES ('A', 18);
INSERT INTO student (name, age) VALUES ('B', 19);
-- 高效:單條 INSERT 多 VALUES(一次往返、批量優化)
INSERT INTO student (name, age) VALUES ('A', 18), ('B', 19), ('C', 20);
<foreach> 實現<insert id="batchInsertStudents" parameterType="java.util.List">
INSERT INTO student (name, sex, address, telephone, tid) VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.name}, #{item.sex}, #{item.address}, #{item.telephone}, #{item.tId})
</foreach>
</insert>
// Mapper Interface
void batchInsertStudents(@Param("list") List<Student> students);
關鍵屬性:
collection:集合參數名(單參數 List 為 list,數組為 array,Map 為 key 名)。item:迭代變量別名。separator:元素分隔符(VALUES 間用逗號)。open/close:整體包裹符(如 ( 和 ))。<insert id="batchInsertOrders" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
<selectKey keyProperty="createTime" order="BEFORE" resultType="java.lang.String">
SELECT NOW() -- 或 CURRENT_TIMESTAMP,統一批次時間
</selectKey>
INSERT INTO orders (user_id, amount, create_time, status) VALUES
<foreach collection="list" item="order" separator=",">
(#{order.userId}, #{order.amount}, #{createTime}, #{order.status})
</foreach>
</insert>
大數據量分批策略:
max_allowed_packet 限制單條 SQL 大小(預設 4MB/64MB)。public void batchInsertLarge(List<Order> orders) {
List<List<Order>> batches = Lists.partition(orders, 500);
for (List<Order> batch : batches) {
orderMapper.batchInsertOrders(batch);
}
}
| 策略 | 配置 | 適用場景 | 注意事項 |
|---|---|---|---|
| 數據庫自增 | useGeneratedKeys="true" keyProperty="id" | MySQL、SQL Server、PostgreSQL (serial) | 批量插入自動回填 ID,JDBC 驅動需支援 |
| 序列/Sequence | <selectKey order="BEFORE">SELECT NEXT VALUE FOR seq</selectKey> | Oracle、PostgreSQL、DB2 | 需數據庫支援序列,批量需預取 ID 段 |
| UUID/雪花算法 | Java 端生成,keyProperty 映射 | 分布式、無中心化 | 無需數據庫協調,索引性能略遜自增 |
| 無主鍵/業務鍵 | 不配置 useGeneratedKeys | 日誌表、關係表 | INSERT 不指定主鍵列 |
${} 僅用於結構性片段(表/列/排序),值參數堅持 #{}。collection="list";多參數用 @Param 或 Map 封裝,collection 指向 key。| 現象 | 原因 | 解決 |
|---|---|---|
| 批量插入主鍵不回填 | JDBC 驅動版本過低 / 未配置 useGeneratedKeys / 數據庫不支援 | 升級驅動、檢查配置、改用 selectKey 預取 ID |
| 動態 SQL 報語法錯誤 | ${} 注入值含特殊字符 / 缺少別名 | 嚴格校驗輸入、SQL 模板化、強制別名 |
| Foreach 參數找不到 | collection 值與實際參數名不匹配 | 單參數 List 用 list,數組用 array,多參數用 @Param key |
| 批量插入超時/內存溢出 | 單批次過大、SQL 過長、事務未提交 | 分批、增加 max_allowed_packet、調大 fetchSize、分批提交 |
MyBatis 的 ${} 賦予了動態 SQL 構建能力,<foreach> 實現了高性能批量寫入。核心原則:動態結構用 ${},業務數值用 #{};批量寫入單條多 VALUES,分批控制大小,主鍵回填自動化。嚴守安全邊界與性能邊界,即可在靈活性與穩健性之間游刃有餘。