MyBatis/MyBatis Plus 动态SQL语法整理
目录
- [if 标签](#if 标签)
- [foreach 标签](#foreach 标签)
- [choose/when/otherwise 标签](#choose/when/otherwise 标签)
- [where 标签](#where 标签)
- [set 标签](#set 标签)
- [trim 标签](#trim 标签)
- [sql 片段与 include](#sql 片段与 include)
- [bind 标签](#bind 标签)
- [CDATA 转义](#CDATA 转义)
- 参数传递方式
- [resultMap 映射](#resultMap 映射)
- 动态SQL综合示例
1. if 标签
基本语法
用于判断条件,如果条件成立则拼接SQL片段。
xml
<if test="条件表达式">
SQL片段
</if>
项目实例
1.1 判断字符串是否为空
xml
<if test="condition.keyword != null and condition.keyword != ''">
and CONCAT(t1.in_notice_no, IFNULL(t1.doc_no,''), t1.classify_type)
like concat('%', #{condition.keyword}, '%')
</if>
1.2 判断数组是否有值
xml
<if test="condition.ids != null and condition.ids.length > 0">
and t1.id IN
<foreach collection="condition.ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
1.3 判断集合是否有值
xml
<if test="condition.inNoticeNoList != null and condition.inNoticeNoList.size() > 0">
and t1.in_notice_no IN
<foreach collection="condition.inNoticeNoList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
1.4 多条件判断
xml
<if test="condition.cellNo != null and condition.cellNo != '' and condition.warehouseNo != null and condition.warehouseNo != ''">
AND (
(chag_type = 2 and cell_no = #{condition.cellNo} and warehouse_no = #{condition.warehouseNo})
or (chag_type = 1 and ifnull(to_cell_no, '') = #{condition.cellNo})
)
</if>
1.5 在 UPDATE 中使用 if
xml
<update id="updateBodyByList">
update in_arrive_body
<set>
<if test="item.currentScanQty != null">
qty = IFNULL(qty, 0) + #{item.currentScanQty},
</if>
<if test="item.statusCode != null">
status_code = #{item.statusCode},
</if>
</set>
where in_arrive_no = #{item.inArriveNo}
</update>
注意事项
- 字符串判断通常需要同时判断
!= null和!= '' - 数组使用
.length属性 - 集合使用
.size()方法 - 可以使用
and、or进行逻辑运算 - if标签中不会自动去除拼接的逗号,需要配合set或trim使用
2. foreach 标签
基本语法
用于遍历集合或数组,生成重复的SQL片段(如IN子句)。
xml
<foreach collection="集合名" item="元素变量名" index="索引变量名"
open="开始符号" close="结束符号" separator="分隔符">
${item}或#{item}
</foreach>
属性说明
- collection: 要遍历的集合或数组
- item: 当前元素的变量名
- index: 当前索引的变量名(可选)
- open: 在遍历开始前添加的字符串
- close: 在遍历结束后添加的字符串
- separator: 元素之间的分隔符
项目实例
2.1 遍历数组生成IN子句
xml
<if test="condition.ids != null and condition.ids.length > 0">
and t1.id IN
<foreach collection="condition.ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
生成SQL: and t1.id IN ('id1', 'id2', 'id3')
2.2 遍历List集合
xml
<if test="condition.outNoticeNoList != null and condition.outNoticeNoList.size() > 0">
and t1.out_notice_no IN
<foreach collection="condition.outNoticeNoList" item="no" separator="," open="(" close=")">
#{no}
</foreach>
</if>
2.3 遍历Map的entrySet
xml
<if test="map != null and map.size() > 0">
<foreach collection="map.entrySet()" item="value" index="key">
<if test="value != null and value != ''">
<choose>
<when test='key == "keyword"'>
and CONCAT(t1.out_notice_no) like concat('%', #{value}, '%')
</when>
<when test='key.contains("indistinct")'>
and t1.${key} like concat('%', #{value}, '%')
</when>
<otherwise>
and t1.${key} = #{value}
</otherwise>
</choose>
</if>
</foreach>
</if>
2.4 批量更新 - CASE WHEN
xml
<update id="updateBatchByIdSelective">
update bm_warehouse_cell
<trim prefix="set" suffixOverrides=",">
<trim prefix="warehouse_no = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.warehouseNo != null and item.warehouseNo != ''">
when id = #{item.id} then #{item.warehouseNo}
</if>
</foreach>
</trim>
<trim prefix="cell_no = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.cellNo != null and item.cellNo != ''">
when id = #{item.id} then #{item.cellNo}
</if>
</foreach>
</trim>
</trim>
where id in
<foreach collection="list" item="item" separator="," open="(" close=")">
#{item.id}
</foreach>
</update>
2.5 批量删除
xml
<delete id="deleteOutNoticeHeadByIds">
delete from out_notice_head
where id in
<foreach collection="reqDto.ids" index="index" separator="," open="(" close=")" item="item">
#{item}
</foreach>
</delete>
2.6 多个IN条件的OR组合
xml
<if test="condition.warehouseNos != null and condition.warehouseNos != ''">
and (warehouse_no in
<foreach collection="condition.warehouseNos" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
OR ifnull(to_warehouse_no, '') in
<foreach collection="condition.warehouseNos" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
)
</if>
注意事项
- 遍历数组时 collection 填写参数名即可
- 遍历List时同样填写参数名
- 遍历Map时使用
map.entrySet(),此时 item 是value,index 是key - 批量操作时要注意性能,避免一次性操作过多数据
3. choose/when/otherwise 标签
基本语法
类似于Java中的switch-case语句,用于多条件分支判断。
xml
<choose>
<when test="条件1">
SQL片段1
</when>
<when test="条件2">
SQL片段2
</when>
<otherwise>
默认SQL片段
</otherwise>
</choose>
项目实例
3.1 基于key的动态条件查询
xml
<foreach collection="map.entrySet()" item="value" index="key">
<if test="value != null and value != ''">
<choose>
<when test='"keyword" == key'>
and CONCAT(t1.out_notice_no, IFNULL(t1.doc_no,''))
like concat('%', #{value}, '%')
</when>
<when test='key.contains("doc_no")'>
and (t1.doc_no = #{value} or t1.out_notice_no = #{value})
</when>
<when test='key.contains("indistinct")'>
and t1.${key} like concat('%', #{value}, '%')
</when>
<when test='key.contains("standard_col")'>
<choose>
<when test='key.equals("standard_col14") or key.equals("standard_col15")'>
and (t1.${key} = #{value} or body.${key} = #{value})
</when>
<otherwise>
and (t1.${key} like concat('%', #{value}, '%'))
</otherwise>
</choose>
</when>
<when test='key == "start_date"'>
and t1.bill_date >= #{value}
</when>
<when test='key == "end_date"'>
and t1.bill_date <= #{value}
</when>
<when test='key == "data_scope_sql"'>
${value}
</when>
<otherwise>
and t1.${key} = #{value}
</otherwise>
</choose>
</if>
</foreach>
3.2 嵌套的choose
xml
<when test='key.contains("standard_col")'>
<choose>
<when test='key.equals("standard_col14") or key.equals("standard_col15")'>
and (t1.${key} = #{value} or body.${key} = #{value})
</when>
<otherwise>
and (t1.${key} like concat('%', #{value}, '%'))
</otherwise>
</choose>
</when>
3.3 字段排序的动态选择
xml
<if test='condition.field != null and condition.field != ""'>
<choose>
<when test='(condition.field.toLowerCase().contains("asc") or condition.field.toLowerCase().contains("desc"))'>
order by ${condition.field}
</when>
<when test="condition.order != null and condition.order != ''">
order by ${condition.field} ${condition.order}
</when>
</choose>
</if>
3.4 复杂条件判断
xml
<choose>
<when test="condition.customerNo != null and condition.customerNo != ''">
> IF(merchant.base_out_shelflife IS NOT NULL, merchant.base_out_shelflife, item.base_out_shelflife)
</when>
<otherwise>
> item.base_out_shelflife
</otherwise>
</choose>
注意事项
- choose中只会执行第一个匹配的when,不会继续执行后续when
- otherwise是可选的,如果没有when匹配且没有otherwise,则不拼接任何SQL
- when可以嵌套choose实现更复杂的条件判断
- 字符串比较使用单引号:
test='"keyword" == key' - 调用方法使用:
test='key.contains("xxx")'
4. where 标签
基本语法
智能处理WHERE子句,自动去除开头多余的 AND 或 OR。
xml
<where>
<if test="条件1">
and 字段1 = #{value1}
</if>
<if test="条件2">
and 字段2 = #{value2}
</if>
</where>
项目实例
4.1 基本用法
xml
<select id="getInNoticeNoByDocAndType">
SELECT DISTINCT t2.in_notice_no
FROM in_notice_body t1
INNER JOIN in_notice_head t2 on t1.in_notice_no = t2.in_notice_no
<where>
<if test="condition.docNo != null and condition.docNo != ''">
and t1.doc_no = #{condition.docNo}
</if>
<if test="condition.orgNo != null and condition.orgNo != ''">
and t2.org_no = #{condition.orgNo}
</if>
<if test="condition.classifyType != null and condition.classifyType != ''">
and t2.classify_type = #{condition.classifyType}
</if>
</where>
</select>
4.2 配合include使用
xml
<select id="getExportFilterList">
select DISTINCT ${columns}
from ${headTable} t1
left join ${bodyTable} t2 on t2.in_notice_no = t1.in_notice_no
<where>
<include refid="getHeadQueryWhere4Export"/>
</where>
</select>
4.3 复杂条件组合
xml
<where>
<if test="condition.tenantsid != null">
and t1.tenantsid = #{condition.tenantsid}
</if>
<include refid="headQueryWhere"/>
</where>
where标签的作用
- 如果标签内有内容,会自动添加 WHERE 关键字
- 自动去除内容开头的 AND 或 OR
- 如果标签内没有内容,则不添加WHERE关键字
注意事项
- 推荐在每个条件前加上
and或or,让where标签自动处理 - 不要在where标签后面手动添加
1=1这样的hack写法
5. set 标签
基本语法
用于UPDATE语句,智能处理SET子句,自动去除末尾多余的逗号。
xml
<update>
update 表名
<set>
<if test="字段1 != null">
字段1 = #{字段1},
</if>
<if test="字段2 != null">
字段2 = #{字段2},
</if>
</set>
where id = #{id}
</update>
项目实例
5.1 动态更新字段
xml
<update id="updateBodyByList">
update in_arrive_body
<set>
<if test="item.currentScanQty != null">
qty = IFNULL(qty, 0) + #{item.currentScanQty},
</if>
<if test="item.referenceQty != null">
reference_qty = IFNULL(reference_qty, 0) + #{item.referenceQty},
</if>
<if test="item.statusCode != null">
status_code = #{item.statusCode},
</if>
<if test="item.updateBy != null">
update_by = #{item.updateBy},
</if>
<if test="item.updateName != null">
update_name = #{item.updateName},
</if>
<if test="item.updateDate != null">
update_date = #{item.updateDate},
</if>
</set>
where (in_arrive_no = #{item.inArriveNo} and seq_no = #{item.seqNo})
and tenantsid = #{tenantsid}
</update>
set标签的作用
- 自动添加 SET 关键字
- 自动去除末尾多余的逗号
- 如果标签内没有内容,不会添加SET关键字(会导致SQL错误)
注意事项
- 每个赋值语句后都加上逗号,set标签会自动处理最后一个逗号
- 确保至少有一个if条件会成立,否则会生成空的SET子句导致SQL错误
6. trim 标签
基本语法
更加灵活的格式化标签,可以自定义前缀、后缀,以及去除指定的前缀或后缀。
xml
<trim prefix="前缀" suffix="后缀" prefixOverrides="要去除的前缀" suffixOverrides="要去除的后缀">
SQL片段
</trim>
属性说明
- prefix: 在内容前添加的字符串
- suffix: 在内容后添加的字符串
- prefixOverrides: 去除内容开头的指定字符串(多个用|分隔)
- suffixOverrides: 去除内容末尾的指定字符串(多个用|分隔)
项目实例
6.1 动态INSERT - 列名部分
xml
<insert id="addOtRobotReportWarn">
insert into ot_robot_report_warn
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="reqDto.id != null">id,</if>
<if test="reqDto.robotType != null">robot_type,</if>
<if test="reqDto.robotTaskId != null">robot_task_id,</if>
<if test="reqDto.alarmDesc != null">alarm_desc,</if>
<if test="reqDto.alarmType != null">alarm_type,</if>
<if test="reqDto.channelDeviceId != null">channel_device_id,</if>
<if test="reqDto.createBy != null">create_by,</if>
<if test="reqDto.createName != null">create_name,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="reqDto.id != null">#{reqDto.id},</if>
<if test="reqDto.robotType != null">#{reqDto.robotType},</if>
<if test="reqDto.robotTaskId != null">#{reqDto.robotTaskId},</if>
<if test="reqDto.alarmDesc != null">#{reqDto.alarmDesc},</if>
<if test="reqDto.alarmType != null">#{reqDto.alarmType},</if>
<if test="reqDto.channelDeviceId != null">#{reqDto.channelDeviceId},</if>
<if test="reqDto.createBy != null">#{reqDto.createBy},</if>
<if test="reqDto.createName != null">#{reqDto.createName},</if>
</trim>
</insert>
6.2 替代set标签
xml
<update id="updateBatchByIdSelective">
update bm_warehouse_cell
<trim prefix="set" suffixOverrides=",">
<trim prefix="warehouse_no = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.warehouseNo != null and item.warehouseNo != ''">
when id = #{item.id} then #{item.warehouseNo}
</if>
</foreach>
</trim>
<trim prefix="cell_no = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.cellNo != null and item.cellNo != ''">
when id = #{item.id} then #{item.cellNo}
</if>
</foreach>
</trim>
</trim>
where id in (...)
</update>
6.3 替代where标签
xml
<select id="selectByCondition">
select * from table_name
<trim prefix="where" prefixOverrides="and|or">
<if test="condition1 != null">
and field1 = #{condition1}
</if>
<if test="condition2 != null">
and field2 = #{condition2}
</if>
</trim>
</select>
trim vs where/set
<where>等价于<trim prefix="where" prefixOverrides="and|or"><set>等价于<trim prefix="set" suffixOverrides=",">- trim 更灵活,可以处理更复杂的场景
注意事项
- prefixOverrides和suffixOverrides中多个值用
|分隔 - 常用于动态INSERT和批量UPDATE场景
- 配合foreach使用时特别有用
7. sql 片段与 include
基本语法
将重复使用的SQL片段定义为可复用的代码块。
xml
<!-- 定义sql片段 -->
<sql id="片段id">
SQL内容
</sql>
<!-- 引用sql片段 -->
<include refid="片段id"/>
项目实例
7.1 定义字段列表
xml
<sql id="Base_Column_List">
id, out_notice_no, warehouse_no,
in_warehouse_no, org_no, doc_org_no,
customer_no, carrier_no, workshop_no,
doc_no, doc_type, classify_type,
create_by, create_name, create_date,
update_by, update_name, update_date
</sql>
<select id="selectById">
select <include refid="Base_Column_List"/>
from out_notice_head
where id = #{id}
</select>
7.2 定义WHERE条件片段
xml
<sql id="headQueryWhere">
<if test="condition.ids != null and condition.ids.length > 0">
and t1.id IN
<foreach collection="condition.ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="condition.outNoticeNoList != null and condition.outNoticeNoList.size() > 0">
and t1.out_notice_no IN
<foreach collection="condition.outNoticeNoList" item="no" separator="," open="(" close=")">
#{no}
</foreach>
</if>
<if test="map != null and map.size() > 0">
<foreach collection="map.entrySet()" item="value" index="key">
<!-- 复杂条件 -->
</foreach>
</if>
</sql>
<select id="getOutNoticeHeadByPda">
select distinct t1.*
from out_notice_head t1
where 1 = 1
<include refid="headQueryWhere"/>
</select>
7.3 定义批次属性字段
xml
<sql id="lotAtt">
t_lot.lot_att01, t_lot.lot_att02, t_lot.lot_att03,
t_lot.lot_att04, t_lot.lot_att05, t_lot.lot_att06,
t_lot.lot_att07, t_lot.lot_att08, t_lot.lot_att09,
t_lot.lot_att10, t_lot.lot_att11, t_lot.lot_att12
</sql>
7.4 复杂查询SQL片段
xml
<sql id="getFifoStockSql">
select DISTINCT t1.*,
(t1.stock_qty - t1.out_qty) as total_stock_qty,
invLot.combination_lot_no
${invLotSql}
from inv_stock t1 FORCE INDEX (UNIQUE_KEY)
INNER JOIN inv_lot invLot ON (t1.item_no = invLot.item_no)
LEFT JOIN bm_warehouse_cell cell ON (t1.warehouse_no = cell.warehouse_no)
where 1=1
<if test="condition.itemNo != null and condition.itemNo != ''">
and t1.item_no = #{condition.itemNo}
</if>
</sql>
7.5 在sql片段中使用变量
xml
<sql id="getInvStockByAllInventorySql">
select t2.org_no,
t1.item_no,
t1.item_name,
${invSql} <!-- 动态列 -->
${lotSql} <!-- 动态批次字段 -->
from bm_item t1
where inv.org_no = #{condition.orgNo}
</sql>
注意事项
- sql片段可以包含动态SQL标签(if、foreach等)
- 可以使用
${}在sql片段中引用外部变量 - sql片段可以互相嵌套引用
- 建议将通用的查询条件、字段列表等提取为sql片段
- sql片段的id在整个mapper文件中必须唯一
8. bind 标签
基本语法
创建一个变量并绑定到上下文,常用于字符串拼接。
xml
<bind name="变量名" value="表达式"/>
项目实例
8.1 模糊查询的参数处理
xml
<select id="selectByKeyword">
<bind name="pattern" value="'%' + keyword + '%'"/>
select * from table_name
where name like #{pattern}
</select>
8.2 复杂表达式
xml
<select id="selectByCondition">
<bind name="startDate" value="condition.startDate + ' 00:00:00'"/>
<bind name="endDate" value="condition.endDate + ' 23:59:59'"/>
select * from table_name
where create_date between #{startDate} and #{endDate}
</select>
优势
- 避免在代码中拼接SQL参数
- 使SQL更加清晰
- 可以在SQL中多次引用同一个变量
注意事项
- bind创建的变量可以在当前select/update/delete标签中使用
- 使用
#{}引用bind定义的变量 - 字符串拼接使用
+号
9. CDATA 转义
基本语法
当SQL中包含 <、>、& 等特殊字符时,需要使用CDATA或转义字符。
xml
<!-- 方式1: CDATA包裹 -->
<![CDATA[ SQL语句包含<、>等 ]]>
<!-- 方式2: 使用转义字符 -->
< 表示 <
> 表示 >
& 表示 &
" 表示 "
' 表示 '
项目实例
9.1 使用CDATA
xml
<if test='key == "pre_status_code"'>
and t1.status_code <![CDATA[<]]> #{value}
</if>
<if test='key == "notice_count"'>
and t1.notice_count <![CDATA[<=]]> #{value}
</if>
<if test="condition.fromType != null and condition.fromType == '10'">
and ifnull(cell.ot_cell_type_flag,'') <![CDATA[<>]]> '20'
</if>
9.2 使用转义字符
xml
<if test='key == "start_date"'>
and t1.bill_date >= #{value}
</if>
<if test='key == "end_date"'>
and t1.bill_date <= #{value}
</if>
<when test='key.equals("standard_col14") or key.equals("standard_col15")'>
and (t1.${key} = #{value} or body.${key} = #{value})
</when>
9.3 复杂条件中使用
xml
and (t1.stock_qty - t1.out_qty) <![CDATA[>]]> 0
AND ifnull(body.apply_qty, 0) <![CDATA[>]]> ifnull(t2.status_qty, 0)
选择建议
- 简单的
<、>推荐使用<、> - 复杂SQL片段推荐使用
<![CDATA[]]> - CDATA中不能嵌套动态标签,需要分开写
注意事项
- CDATA不能包含动态SQL标签(if、foreach等)
- 如果需要在条件中使用
<、>,必须转义或使用CDATA
10. 参数传递方式
10.1 #{} 与 ${} 的区别
#{} - 预编译参数(推荐)
- 会进行预编译,防止SQL注入
- 参数会被当作字符串,自动添加引号
- 适用于传递值(where条件、insert values等)
xml
<!-- 生成: where id = '123' -->
where id = #{id}
<!-- 生成: where name = 'John' -->
where name = #{userName}
${} - 字符串替换(慎用)
- 直接进行字符串替换,存在SQL注入风险
- 不会添加引号
- 适用于传递表名、字段名、order by等
xml
<!-- 生成: select * from user_table -->
select * from ${tableName}
<!-- 生成: order by create_date desc -->
order by ${field} ${order}
<!-- 生成: and t1.item_no = 'ABC' -->
and t1.${columnName} = #{value}
项目实例
10.2 使用 #{} 传递值
xml
<if test="condition.itemNo != null and condition.itemNo != ''">
and t1.item_no = #{condition.itemNo}
</if>
<if test="condition.statusCode != null">
and t1.status_code = #{condition.statusCode}
</if>
10.3 使用 ${} 传递字段名和表名
xml
<!-- 动态字段名 -->
<when test='key == "data_scope_sql"'>
${value}
</when>
<!-- 动态排序 -->
<if test="condition.field != null and condition.order != null">
order by ${condition.field} ${condition.order}
</if>
<!-- 动态表名 -->
<select id="getExportFilterList">
select DISTINCT ${columns}
from ${headTable} t1
left join ${bodyTable} t2 on t2.in_notice_no = t1.in_notice_no
</select>
<!-- 动态字段查询 -->
<when test='key.contains("indistinct")'>
and t1.${key} like concat('%', #{value}, '%')
</when>
10.4 组合使用
xml
<!-- ${}用于字段名,#{}用于值 -->
<foreach collection="map.entrySet()" item="value" index="key">
<otherwise>
and t1.${key} = #{value}
</otherwise>
</foreach>
<!-- 动态列名和条件值 -->
<if test="key.contains('standard_col')">
and (t1.${key} like concat('%', #{value}, '%'))
</if>
10.5 传递对象属性
xml
<!-- 直接访问对象属性 -->
where id = #{item.id}
and name = #{item.userName}
<!-- 嵌套对象 -->
where org_no = #{condition.orgInfo.orgNo}
10.6 传递Map
xml
<!-- Map的key作为参数名 -->
<select id="selectByMap" parameterType="map">
select * from user
where name = #{userName}
and age = #{userAge}
</select>
安全性建议
- 值传递一律使用
#{},防止SQL注入 - 仅在必要时使用
${}(表名、字段名、order by) - 使用
${}时,务必在代码层面进行参数校验
11. resultMap 映射
基本语法
用于定义数据库字段到Java对象属性的映射关系。
xml
<resultMap id="映射id" type="返回类型">
<id property="主键属性" column="主键列名" jdbcType="JDBC类型"/>
<result property="属性名" column="列名" jdbcType="JDBC类型"/>
</resultMap>
项目实例
11.1 基础映射
xml
<resultMap id="BaseResultMap" type="com.digiwin.zhilink.ilcwmsplus.wms.entity.InvStockDto">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="orgNo" column="org_no" jdbcType="VARCHAR"/>
<result property="warehouseNo" column="warehouse_no" jdbcType="VARCHAR"/>
<result property="cellNo" column="cell_no" jdbcType="VARCHAR"/>
<result property="itemNo" column="item_no" jdbcType="VARCHAR"/>
<result property="stockQty" column="stock_qty" jdbcType="DECIMAL"/>
<result property="badFlag" column="bad_flag" jdbcType="TINYINT"/>
<result property="statusCode" column="status_code" jdbcType="TINYINT"/>
<result property="createDate" column="create_date" jdbcType="TIMESTAMP"/>
<result property="version" column="version" jdbcType="BIGINT"/>
</resultMap>
11.2 使用resultMap
xml
<!-- 使用resultMap -->
<select id="selectById" resultMap="BaseResultMap">
select * from inv_stock where id = #{id}
</select>
<!-- 使用resultType (简单映射) -->
<select id="selectById" resultType="com.digiwin.zhilink.ilcwmsplus.wms.entity.InvStockDto">
select * from inv_stock where id = #{id}
</select>
resultMap vs resultType
- resultMap: 适用于复杂映射,需要明确定义字段对应关系
- resultType: 适用于简单映射,字段名与属性名一致(或下划线转驼峰)
注意事项
- id标签用于标识主键,可以优化性能
- jdbcType可以省略,MyBatis会自动推断
- 如果列名和属性名遵循下划线转驼峰规则,可以直接用resultType
- 项目中大部分查询使用的是 resultType="Dto类"
12. 动态SQL综合示例
12.1 复杂查询示例
xml
<select id="getOutNoticeHeadByPda" resultType="com.digiwin.zhilink.ilcwmsplus.wms.entity.OutNoticeHeadDto">
select distinct t1.*,
warehouse.fifo_control,
concat(t1.customer_no, '-', t1.customer_short_name) as customer_code_name,
concat(t1.carrier_no, '-', t1.carrier_short_name) as carrier_code_name
from out_notice_head t1
left join out_notice_body body on (body.out_notice_no = t1.out_notice_no)
left join bm_warehouse warehouse on (warehouse.warehouse_no = t1.warehouse_no)
where 1 = 1
<!-- 引用sql片段 -->
<include refid="headQueryWhere"/>
<!-- 动态排序 -->
<if test="condition.field != null and condition.field != '' and condition.order != null and condition.order != ''">
order by ${condition.field} ${condition.order}
</if>
</select>
<sql id="headQueryWhere">
<!-- 数组条件 -->
<if test="condition.ids != null and condition.ids.length > 0">
and t1.id IN
<foreach collection="condition.ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<!-- List条件 -->
<if test="condition.statusCodeList != null and condition.statusCodeList.size() > 0">
and t1.status_code IN
<foreach collection="condition.statusCodeList" item="status" separator="," open="(" close=")">
#{status}
</foreach>
</if>
<!-- Map动态条件 -->
<if test="map != null and map.size() > 0">
<foreach collection="map.entrySet()" item="value" index="key">
<if test="value != null and value != ''">
<choose>
<when test='"keyword" == key'>
and CONCAT(t1.out_notice_no, IFNULL(t1.doc_no,''))
like concat('%', #{value}, '%')
</when>
<when test='key.contains("indistinct")'>
and t1.${key} like concat('%', #{value}, '%')
</when>
<when test='key == "start_date"'>
and t1.bill_date >= #{value}
</when>
<when test='key == "end_date"'>
and t1.bill_date <= #{value}
</when>
<otherwise>
and t1.${key} = #{value}
</otherwise>
</choose>
</if>
</foreach>
</if>
</sql>
12.2 复杂更新示例
xml
<update id="updateBatchByIdSelective" parameterType="java.util.List">
update bm_warehouse_cell
<trim prefix="set" suffixOverrides=",">
<!-- 批量更新多个字段 -->
<trim prefix="warehouse_no = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.warehouseNo != null and item.warehouseNo != ''">
when id = #{item.id} then #{item.warehouseNo}
</if>
</foreach>
</trim>
<trim prefix="cell_no = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.cellNo != null and item.cellNo != ''">
when id = #{item.id} then #{item.cellNo}
</if>
</foreach>
</trim>
<trim prefix="status_code = case" suffix="end,">
<foreach collection="list" index="index" item="item">
<if test="item.statusCode != null">
when id = #{item.id} then #{item.statusCode}
</if>
</foreach>
</trim>
</trim>
where id in
<foreach collection="list" item="item" separator="," open="(" close=")">
#{item.id}
</foreach>
</update>
12.3 动态插入示例
xml
<insert id="insertSelective">
insert into table_name
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="name != null">name,</if>
<if test="status != null">status,</if>
<if test="createDate != null">create_date,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="name != null">#{name},</if>
<if test="status != null">#{status},</if>
<if test="createDate != null">#{createDate},</if>
</trim>
</insert>
最佳实践建议
1. 安全性
- 值传递使用
#{},避免SQL注入 - 表名、字段名使用
${}时必须做白名单校验 - 避免直接拼接用户输入到SQL中
2. 性能优化
- 合理使用
<if>避免无效条件 - 大量数据批量操作时注意分批处理
- 复杂查询使用 sql 片段提高可维护性
- 注意 foreach 生成的 IN 子句不要过长(建议不超过1000个)
3. 可维护性
- 将重复的SQL片段提取为
<sql> - 复杂条件拆分为多个小的sql片段
- 给sql片段和resultMap取有意义的id
- 适当添加注释说明复杂逻辑
4. 命名规范
- sql片段id使用小驼峰或下划线命名
- resultMap的id通常为
BaseResultMap或xxxResultMap - 方法名遵循:select/get/query/list/insert/update/delete开头
5. 参数传递
- 单个参数直接使用:
#{参数名}或#{_parameter} - 多个参数使用
@Param注解或封装为对象 - 复杂查询条件封装为 Condition 对象
常见问题
Q1: foreach中生成的SQL过长怎么办?
A: 将数据分批处理,每批不超过1000个,或使用临时表。
Q2: ${} 和 #{} 该如何选择?
A:
- 传值用
#{}(where条件、insert values) - 传字段名、表名、order by用
${}
Q3: where标签没有生效?
A : 确保每个条件前都有 and 或 or,where标签会自动去除第一个。
Q4: trim标签中suffixOverrides不生效?
A : 检查是否有空格,suffixOverrides="," 不会去除 , (逗号+空格)。
Q5: choose中多个when都匹配会执行几个?
A: 只会执行第一个匹配的when,类似Java的switch-case。
总结
MyBatis动态SQL标签让我们能够灵活地构建SQL语句,主要标签包括:
| 标签 | 作用 | 使用场景 |
|---|---|---|
<if> |
条件判断 | 动态WHERE条件、动态字段 |
<foreach> |
循环遍历 | IN查询、批量操作 |
<choose>/<when>/<otherwise> |
多条件分支 | 类似switch-case |
<where> |
WHERE子句 | 自动处理AND/OR |
<set> |
SET子句 | 动态UPDATE |
<trim> |
字符串裁剪 | 自定义前后缀处理 |
<sql>/<include> |
SQL片段 | 代码复用 |
<bind> |
变量绑定 | 字符串拼接 |
掌握这些标签的使用,可以编写出灵活、安全、高效的SQL语句。