17.MyBatis动态SQL语法整理

MyBatis/MyBatis Plus 动态SQL语法整理

目录

  1. [if 标签](#if 标签)
  2. [foreach 标签](#foreach 标签)
  3. [choose/when/otherwise 标签](#choose/when/otherwise 标签)
  4. [where 标签](#where 标签)
  5. [set 标签](#set 标签)
  6. [trim 标签](#trim 标签)
  7. [sql 片段与 include](#sql 片段与 include)
  8. [bind 标签](#bind 标签)
  9. [CDATA 转义](#CDATA 转义)
  10. 参数传递方式
  11. [resultMap 映射](#resultMap 映射)
  12. 动态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() 方法
  • 可以使用 andor 进行逻辑运算
  • 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 &lt;= #{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子句,自动去除开头多余的 ANDOR

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标签的作用

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

注意事项

  • 推荐在每个条件前加上 andor,让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标签的作用

  1. 自动添加 SET 关键字
  2. 自动去除末尾多余的逗号
  3. 如果标签内没有内容,不会添加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: 使用转义字符 -->
&lt;   表示 <
&gt;   表示 >
&amp;  表示 &
&quot; 表示 "
' 表示 '

项目实例

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 &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 复杂条件中使用
xml 复制代码
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等)
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 &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 复杂更新示例

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通常为 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语句。


相关推荐
llxxyy卢2 小时前
基于时间的 SQL 盲注-延时判断和基于布尔的 SQL 盲注
数据库·sql·oracle
SheepHappy2 小时前
MyBatis-Plus 源码阅读(二)代码生成器原理深度剖析
java·源码阅读
雨白2 小时前
重识 Java IO、NIO 与 OkIO
android·java
light_in_hand2 小时前
内存区域划分——垃圾回收
java·jvm·算法
金銀銅鐵2 小时前
[Java] JDK 9 新变化之 Convenience Factory Methods for Collections
java·后端
用户7406696136253 小时前
入门并理解Java模块化系统(JPMS)
java
金銀銅鐵3 小时前
[Java] 用 Swing 生成一个最大公约数计算器
java·后端
小安同学iter3 小时前
SQL50+Hot100系列(11.7)
java·算法·leetcode·hot100·sql50
珊珊而川3 小时前
MAC-SQL:黄金标准错误
数据库·sql