17.MyBatis动态SQL语法整理

MyBatis/MyBatis Plus 动态SQL语法整理

目录

  1. if 标签
  2. foreach 标签
  3. choose/when/otherwise 标签
  4. where 标签
  5. set 标签
  6. trim 标签
  7. sql 片段与 include
  8. bind 标签
  9. CDATA 转义
  10. 参数传递方式
  11. resultMap 映射
  12. 动态SQL综合示例

1. if 标签

基本语法

用于判断条件,如果条件成立则拼接SQL片段。

<if test="条件表达式">
    SQL片段
</if>

项目实例

1.1 判断字符串是否为空
<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 判断数组是否有值
<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 判断集合是否有值
<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 多条件判断
<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
<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() 方法
  • 可以使用 andor 进行逻辑运算
  • if标签中不会自动去除拼接的逗号,需要配合set或trim使用

2. foreach 标签

基本语法

用于遍历集合或数组,生成重复的SQL片段(如IN子句)。

<foreach collection="集合名" item="元素变量名" index="索引变量名" 
         open="开始符号" close="结束符号" separator="分隔符">
    ${item}或#{item}
</foreach>

属性说明

  • collection: 要遍历的集合或数组
  • item: 当前元素的变量名
  • index: 当前索引的变量名(可选)
  • open: 在遍历开始前添加的字符串
  • close: 在遍历结束后添加的字符串
  • separator: 元素之间的分隔符

项目实例

2.1 遍历数组生成IN子句
<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集合
<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
<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
<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 批量删除
<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组合
<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语句,用于多条件分支判断。

<choose>
    <when test="条件1">
        SQL片段1
    </when>
    <when test="条件2">
        SQL片段2
    </when>
    <otherwise>
        默认SQL片段
    </otherwise>
</choose>

项目实例

3.1 基于key的动态条件查询
<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 &lt;= #{value}
            </when>
            <when test='key == "data_scope_sql"'>
                ${value}
            </when>
            <otherwise>
                and t1.${key} = #{value}
            </otherwise>
        </choose>
    </if>
</foreach>
3.2 嵌套的choose
<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 字段排序的动态选择
<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 复杂条件判断
<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子句,自动去除开头多余的 ANDOR

<where>
    <if test="条件1">
        and 字段1 = #{value1}
    </if>
    <if test="条件2">
        and 字段2 = #{value2}
    </if>
</where>

项目实例

4.1 基本用法
<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使用
<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 复杂条件组合
<where>
    <if test="condition.tenantsid != null">
        and t1.tenantsid = #{condition.tenantsid}
    </if>
    <include refid="headQueryWhere"/>
</where>

where标签的作用

  1. 如果标签内有内容,会自动添加 WHERE 关键字
  2. 自动去除内容开头的 AND 或 OR
  3. 如果标签内没有内容,则不添加WHERE关键字

注意事项

  • 推荐在每个条件前加上 andor,让where标签自动处理
  • 不要在where标签后面手动添加 1=1 这样的hack写法

5. set 标签

基本语法

用于UPDATE语句,智能处理SET子句,自动去除末尾多余的逗号。

<update>
    update 表名
    <set>
        <if test="字段1 != null">
            字段1 = #{字段1},
        </if>
        <if test="字段2 != null">
            字段2 = #{字段2},
        </if>
    </set>
    where id = #{id}
</update>

项目实例

5.1 动态更新字段
<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标签的作用

  1. 自动添加 SET 关键字
  2. 自动去除末尾多余的逗号
  3. 如果标签内没有内容,不会添加SET关键字(会导致SQL错误)

注意事项

  • 每个赋值语句后都加上逗号,set标签会自动处理最后一个逗号
  • 确保至少有一个if条件会成立,否则会生成空的SET子句导致SQL错误

6. trim 标签

基本语法

更加灵活的格式化标签,可以自定义前缀、后缀,以及去除指定的前缀或后缀。

<trim prefix="前缀" suffix="后缀" prefixOverrides="要去除的前缀" suffixOverrides="要去除的后缀">
    SQL片段
</trim>

属性说明

  • prefix: 在内容前添加的字符串
  • suffix: 在内容后添加的字符串
  • prefixOverrides: 去除内容开头的指定字符串(多个用|分隔)
  • suffixOverrides: 去除内容末尾的指定字符串(多个用|分隔)

项目实例

6.1 动态INSERT - 列名部分
<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标签
<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标签
<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片段定义为可复用的代码块。

<!-- 定义sql片段 -->
<sql id="片段id">
    SQL内容
</sql>

<!-- 引用sql片段 -->
<include refid="片段id"/>

项目实例

7.1 定义字段列表
<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条件片段
<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 定义批次属性字段
<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片段
<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片段中使用变量
<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 标签

基本语法

创建一个变量并绑定到上下文,常用于字符串拼接。

<bind name="变量名" value="表达式"/>

项目实例

8.1 模糊查询的参数处理
<select id="selectByKeyword">
    <bind name="pattern" value="'%' + keyword + '%'"/>
    select * from table_name
    where name like #{pattern}
</select>
8.2 复杂表达式
<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或转义字符。

<!-- 方式1: CDATA包裹 -->
<![CDATA[ SQL语句包含<、>等 ]]>

<!-- 方式2: 使用转义字符 -->
&lt;   表示 <
&gt;   表示 >
&amp;  表示 &
&quot; 表示 "
&apos; 表示 '

项目实例

9.1 使用CDATA
<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 使用转义字符
<if test='key == "start_date"'>
    and t1.bill_date &gt;= #{value}
</if>

<if test='key == "end_date"'>
    and t1.bill_date &lt;= #{value}
</if>

<when test='key.equals("standard_col14") or key.equals("standard_col15")'>
    and (t1.${key} = #{value} or body.${key} = #{value})
</when>
9.3 复杂条件中使用
and (t1.stock_qty - t1.out_qty) <![CDATA[>]]> 0

AND ifnull(body.apply_qty, 0) <![CDATA[>]]> ifnull(t2.status_qty, 0)

选择建议

  • 简单的 <> 推荐使用 &lt;&gt;
  • 复杂SQL片段推荐使用 <![CDATA[]]>
  • CDATA中不能嵌套动态标签,需要分开写

注意事项

  • CDATA不能包含动态SQL标签(if、foreach等)
  • 如果需要在条件中使用 <>,必须转义或使用CDATA

10. 参数传递方式

10.1 #{} 与 ${} 的区别

#{} - 预编译参数(推荐)
  • 会进行预编译,防止SQL注入
  • 参数会被当作字符串,自动添加引号
  • 适用于传递值(where条件、insert values等)
<!-- 生成: where id = '123' -->
where id = #{id}

<!-- 生成: where name = 'John' -->
where name = #{userName}
${} - 字符串替换(慎用)
  • 直接进行字符串替换,存在SQL注入风险
  • 不会添加引号
  • 适用于传递表名、字段名、order by等
<!-- 生成: 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 使用 #{} 传递值
<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 使用 ${} 传递字段名和表名
<!-- 动态字段名 -->
<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 组合使用
<!-- ${}用于字段名,#{}用于值 -->
<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 传递对象属性

<!-- 直接访问对象属性 -->
where id = #{item.id}
and name = #{item.userName}

<!-- 嵌套对象 -->
where org_no = #{condition.orgInfo.orgNo}

10.6 传递Map

<!-- Map的key作为参数名 -->
<select id="selectByMap" parameterType="map">
    select * from user
    where name = #{userName}
    and age = #{userAge}
</select>

安全性建议

  • 值传递一律使用 #{},防止SQL注入
  • 仅在必要时使用 ${}(表名、字段名、order by)
  • 使用 ${} 时,务必在代码层面进行参数校验

11. resultMap 映射

基本语法

用于定义数据库字段到Java对象属性的映射关系。

<resultMap id="映射id" type="返回类型">
    <id property="主键属性" column="主键列名" jdbcType="JDBC类型"/>
    <result property="属性名" column="列名" jdbcType="JDBC类型"/>
</resultMap>

项目实例

11.1 基础映射
<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
<!-- 使用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 复杂查询示例

<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 &gt;= #{value}
                    </when>
                    <when test='key == "end_date"'>
                        and t1.bill_date &lt;= #{value}
                    </when>
                    <otherwise>
                        and t1.${key} = #{value}
                    </otherwise>
                </choose>
            </if>
        </foreach>
    </if>
</sql>

12.2 复杂更新示例

<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 动态插入示例

<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通常为 BaseResultMapxxxResultMap
  • 方法名遵循: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: 确保每个条件前都有 andor,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语句。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值