MyBatis 实战指南:特殊符号处理与高效批量操作

在 MyBatis 开发中,处理 SQL 特殊符号(如 ><)是基础必知内容,而面对大量数据的批量插入与更新,如何编写高效且规范的 XML 映射文件则是进阶关键。本文将分两部分进行详解。

第一部分:MyBatis 中大于、小于、等于的写法

在 XML 文件中,<> 等符号会被解析器误认为是标签的开始或结束,因此必须进行转义或使用 CDATA 块。

方法一:实体字符转义(推荐用于简单比较)

将特殊符号替换为对应的 HTML 实体字符。这是最通用的方法,适用于所有 XML 解析环境。

表格

原符号 含义 替换符号 (Entity)
< 小于 &lt;
<= 小于等于 &lt;=
> 大于 &gt;
>= 大于等于 &gt;=
& &amp;
' 单引号 '
" 双引号 &quot;

代码示例:

复制代码
<!-- 查询创建时间在 startTime 和 endTime 之间的数据 -->
<select id="selectByTimeRange" resultType="com.example.Entity">
    SELECT * FROM table_name
    WHERE create_date_time &gt;= #{startTime} 
      AND create_date_time &lt;= #{endTime}
</select>

方法二:CDATA 区段(推荐用于复杂 SQL 片段)

使用 <![CDATA[ ... ]]> 包裹 SQL 片段。CDATA 中的内容不会被 XML 解析器解析,直接作为纯文本处理。这种方法在书写复杂条件或包含多个特殊符号时更清晰。

代码示例:

复制代码
<!-- 使用 CDATA 包裹比较运算符 -->
<select id="selectByTimeRangeCdata" resultType="com.example.Entity">
    SELECT * FROM table_name
    WHERE create_date_time <![CDATA[ >= ]]> #{startTime} 
      AND create_date_time <![CDATA[ <= ]]> #{endTime}
</select>

💡 最佳实践建议

  • 简单的 >< 比较,推荐使用 方法一,代码更紧凑。
  • 如果 SQL 片段中包含大量特殊符号,或者为了代码可读性,推荐使用 方法二

第二部分:批量操作实战案例(插入与更新)

以下是一个完整的业务场景:对考核指标表(sjjh_bzzbk_list)及其备份表进行清空、批量插入和批量更新操作。

1. Mapper 接口定义

复制代码
@Mapper
public interface SjjhBzzbkListMapper extends BaseMapper<SjjhBzzbkList> {

    /**
     * 清空备份表
     */
    void truncateDataBak();

    /**
     * 批量插入备份表
     */
    void batchInsertBak(@Param("batchEntities") List<SjjhBzzbkListBak> batchEntities);

    /**
     * 批量插入当前表
     * @return 受影响的行数
     */
    int batchInsert(@Param("batchEntities") List<SjjhBzzbkList> batchEntities);

    /**
     * 批量更新当前表
     * @return 受影响的行数
     */
    int updateBatchById(@Param("batchEntities") List<SjjhBzzbkList> batchEntities);
}

2. XML 映射文件实现

✅ 优化点说明:
  1. TRUNCATE vs DELETE :清空表建议使用 TRUNCATE TABLE,速度更快且重置自增主键(若无需触发器)。
  2. IFNULL 的处理 :原代码大量使用 IFNULL(..., '')。这会将数据库中的 NULL 强制转为空字符串。请确认业务需求 :如果数据库字段允许 NULL 且需要区分"无值"和"空值",建议去掉 IFNULL,直接让 MyBatis 处理 null。下文保留原逻辑以符合你的业务现状,但增加了 jdbcType 规范。
  3. 批量更新的性能陷阱 :原代码使用 <foreach separator=";"> 拼接多条 UPDATE 语句。
    • 风险 :默认 MySQL JDBC 驱动禁止一次执行多条 SQL,需在连接串配置 allowMultiQueries=true
    • 性能:交互次数虽少,但解析开销大。
    • 优化方案 :下文提供了两种方案 。方案 A 保留你的原逻辑(需配置驱动);方案 B 推荐使用 MySQL 特有的 ON DUPLICATE KEY UPDATE 语法(性能更优,无需特殊配置,但需表有唯一索引/主键)。
XML 代码内容
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.SjjhBzzbkListMapper">

    <!-- 1. 清空备份表 (使用 TRUNCATE 效率更高) -->
    <delete id="truncateDataBak">
        TRUNCATE TABLE sjjh_bzzbk_list_bak
    </delete>

    <!-- 2. 批量插入备份表 -->
    <insert id="batchInsertBak" parameterType="java.util.List">
        INSERT INTO sjjh_bzzbk_list_bak (
            cycle_id, kpi_code, code_hierarchy, kpi_name, kpi_desc, statement_desc,
            art_statement, page_statement, unit, kpi_stats, kpi_resource, kpi_resource_detail,
            kpi_resource_code, kpi_state, domain_name, create_time, lzgx_code, cx_code,
            gzdp_code, wxy_code, jsc_code, clzx_code, fwdp_code, gldp_code, ywjfdp_code,
            ictdp_code, zwjlkb_code, del_flg
        ) VALUES
        <foreach collection="batchEntities" item="item" separator=",">
            (
                #{item.cycleId, jdbcType=VARCHAR},
                #{item.kpiCode, jdbcType=VARCHAR},
                #{item.codeHierarchy, jdbcType=VARCHAR},
                #{item.kpiName, jdbcType=VARCHAR},
                #{item.kpiDesc, jdbcType=VARCHAR},
                #{item.statementDesc, jdbcType=VARCHAR},
                #{item.artStatement, jdbcType=VARCHAR},
                #{item.pageStatement, jdbcType=VARCHAR},
                #{item.unit, jdbcType=VARCHAR},
                #{item.kpiStats, jdbcType=VARCHAR},
                #{item.kpiResource, jdbcType=VARCHAR},
                #{item.kpiResourceDetail, jdbcType=VARCHAR},
                #{item.kpiResourceCode, jdbcType=VARCHAR},
                #{item.kpiState, jdbcType=VARCHAR},
                #{item.domainName, jdbcType=VARCHAR},
                #{item.createTime, jdbcType=TIMESTAMP},
                #{item.lzgxCode, jdbcType=VARCHAR},
                #{item.cxCode, jdbcType=VARCHAR},
                #{item.gzdpCode, jdbcType=VARCHAR},
                #{item.wxyCode, jdbcType=VARCHAR},
                #{item.jscCode, jdbcType=VARCHAR},
                #{item.clzxCode, jdbcType=VARCHAR},
                #{item.fwdpCode, jdbcType=VARCHAR},
                #{item.gldpCode, jdbcType=VARCHAR},
                #{item.ywjfdpCode, jdbcType=VARCHAR},
                #{item.ictdpCode, jdbcType=VARCHAR},
                #{item.zwjlkbCode, jdbcType=VARCHAR},
                COALESCE(#{item.delFlg, jdbcType=CHAR}, '0') 
            )
        </foreach>
    </insert>

    <!-- 3. 批量插入当前表 (逻辑同上) -->
    <insert id="batchInsert" parameterType="java.util.List">
        INSERT INTO sjjh_bzzbk_list (
            cycle_id, kpi_code, code_hierarchy, kpi_name, kpi_desc, statement_desc,
            art_statement, page_statement, unit, kpi_stats, kpi_resource, kpi_resource_detail,
            kpi_resource_code, kpi_state, domain_name, create_time, lzgx_code, cx_code,
            gzdp_code, wxy_code, jsc_code, clzx_code, fwdp_code, gldp_code, ywjfdp_code,
            ictdp_code, zwjlkb_code, del_flg
        ) VALUES
        <foreach collection="batchEntities" item="item" separator=",">
            (
                #{item.cycleId, jdbcType=VARCHAR},
                #{item.kpiCode, jdbcType=VARCHAR},
                #{item.codeHierarchy, jdbcType=VARCHAR},
                #{item.kpiName, jdbcType=VARCHAR},
                #{item.kpiDesc, jdbcType=VARCHAR},
                #{item.statementDesc, jdbcType=VARCHAR},
                #{item.artStatement, jdbcType=VARCHAR},
                #{item.pageStatement, jdbcType=VARCHAR},
                #{item.unit, jdbcType=VARCHAR},
                #{item.kpiStats, jdbcType=VARCHAR},
                #{item.kpiResource, jdbcType=VARCHAR},
                #{item.kpiResourceDetail, jdbcType=VARCHAR},
                #{item.kpiResourceCode, jdbcType=VARCHAR},
                #{item.kpiState, jdbcType=VARCHAR},
                #{item.domainName, jdbcType=VARCHAR},
                #{item.createTime, jdbcType=TIMESTAMP},
                #{item.lzgxCode, jdbcType=VARCHAR},
                #{item.cxCode, jdbcType=VARCHAR},
                #{item.gzdpCode, jdbcType=VARCHAR},
                #{item.wxyCode, jdbcType=VARCHAR},
                #{item.jscCode, jdbcType=VARCHAR},
                #{item.clzxCode, jdbcType=VARCHAR},
                #{item.fwdpCode, jdbcType=VARCHAR},
                #{item.gldpCode, jdbcType=VARCHAR},
                #{item.ywjfdpCode, jdbcType=VARCHAR},
                #{item.ictdpCode, jdbcType=VARCHAR},
                #{item.zwjlkbCode, jdbcType=VARCHAR},
                COALESCE(#{item.delFlg, jdbcType=CHAR}, '0')
            )
        </foreach>
    </insert>

    <!-- 
      4. 批量更新方案 A:多语句模式 (原逻辑优化版)
      注意:需要在 JDBC 连接 URL 中添加 allowMultiQueries=true
      例如:jdbc:mysql://localhost:3306/db?allowMultiQueries=true
    -->
    <update id="updateBatchById" parameterType="java.util.List">
        <foreach collection="batchEntities" item="item" separator=";">
            UPDATE sjjh_bzzbk_list
            <set>
                cycle_id = COALESCE(#{item.cycleId, jdbcType=VARCHAR}, ''),
                kpi_code = COALESCE(#{item.kpiCode, jdbcType=VARCHAR}, ''),
                code_hierarchy = COALESCE(#{item.codeHierarchy, jdbcType=VARCHAR}, ''),
                kpi_name = COALESCE(#{item.kpiName, jdbcType=VARCHAR}, ''),
                kpi_desc = COALESCE(#{item.kpiDesc, jdbcType=VARCHAR}, ''),
                statement_desc = COALESCE(#{item.statementDesc, jdbcType=VARCHAR}, ''),
                art_statement = COALESCE(#{item.artStatement, jdbcType=VARCHAR}, ''),
                page_statement = COALESCE(#{item.pageStatement, jdbcType=VARCHAR}, ''),
                unit = COALESCE(#{item.unit, jdbcType=VARCHAR}, ''),
                kpi_stats = COALESCE(#{item.kpiStats, jdbcType=VARCHAR}, ''),
                kpi_resource = COALESCE(#{item.kpiResource, jdbcType=VARCHAR}, ''),
                kpi_resource_detail = COALESCE(#{item.kpiResourceDetail, jdbcType=VARCHAR}, ''),
                kpi_resource_code = COALESCE(#{item.kpiResourceCode, jdbcType=VARCHAR}, ''),
                kpi_state = COALESCE(#{item.kpiState, jdbcType=VARCHAR}, ''),
                domain_name = COALESCE(#{item.domainName, jdbcType=VARCHAR}, ''),
                create_time = #{item.createTime, jdbcType=TIMESTAMP},
                lzgx_code = COALESCE(#{item.lzgxCode, jdbcType=VARCHAR}, ''),
                cx_code = COALESCE(#{item.cxCode, jdbcType=VARCHAR}, ''),
                gzdp_code = COALESCE(#{item.gzdpCode, jdbcType=VARCHAR}, ''),
                wxy_code = COALESCE(#{item.wxyCode, jdbcType=VARCHAR}, ''),
                jsc_code = COALESCE(#{item.jscCode, jdbcType=VARCHAR}, ''),
                clzx_code = COALESCE(#{item.clzxCode, jdbcType=VARCHAR}, ''),
                fwdp_code = COALESCE(#{item.fwdpCode, jdbcType=VARCHAR}, ''),
                gldp_code = COALESCE(#{item.gldpCode, jdbcType=VARCHAR}, ''),
                ywjfdp_code = COALESCE(#{item.ywjfdpCode, jdbcType=VARCHAR}, ''),
                ictdp_code = COALESCE(#{item.ictdpCode, jdbcType=VARCHAR}, ''),
                zwjlkb_code = COALESCE(#{item.zwjlkbCode, jdbcType=VARCHAR}, ''),
                del_flg = COALESCE(#{item.delFlg, jdbcType=CHAR}, '0')
            </set>
            WHERE id = #{item.id}
        </foreach>
    </update>

    <!-- 
      5. 批量更新方案 B:ON DUPLICATE KEY UPDATE (高性能推荐)
      前提:表中 id 为主键或有唯一索引。
      优势:单次 SQL 交互,无需 allowMultiQueries 配置,性能优于方案 A。
      逻辑:尝试插入,若主键冲突则执行更新。
    -->
    <!-- 
    <insert id="updateBatchById" parameterType="java.util.List">
        INSERT INTO sjjh_bzzbk_list (
            id, cycle_id, kpi_code, code_hierarchy, kpi_name, kpi_desc, statement_desc,
            art_statement, page_statement, unit, kpi_stats, kpi_resource, kpi_resource_detail,
            kpi_resource_code, kpi_state, domain_name, create_time, lzgx_code, cx_code,
            gzdp_code, wxy_code, jsc_code, clzx_code, fwdp_code, gldp_code, ywjfdp_code,
            ictdp_code, zwjlkb_code, del_flg
        ) VALUES
        <foreach collection="batchEntities" item="item" separator=",">
            (
                #{item.id, jdbcType=BIGINT},
                #{item.cycleId, jdbcType=VARCHAR},
                #{item.kpiCode, jdbcType=VARCHAR},
                #{item.codeHierarchy, jdbcType=VARCHAR},
                #{item.kpiName, jdbcType=VARCHAR},
                #{item.kpiDesc, jdbcType=VARCHAR},
                #{item.statementDesc, jdbcType=VARCHAR},
                #{item.artStatement, jdbcType=VARCHAR},
                #{item.pageStatement, jdbcType=VARCHAR},
                #{item.unit, jdbcType=VARCHAR},
                #{item.kpiStats, jdbcType=VARCHAR},
                #{item.kpiResource, jdbcType=VARCHAR},
                #{item.kpiResourceDetail, jdbcType=VARCHAR},
                #{item.kpiResourceCode, jdbcType=VARCHAR},
                #{item.kpiState, jdbcType=VARCHAR},
                #{item.domainName, jdbcType=VARCHAR},
                #{item.createTime, jdbcType=TIMESTAMP},
                #{item.lzgxCode, jdbcType=VARCHAR},
                #{item.cxCode, jdbcType=VARCHAR},
                #{item.gzdpCode, jdbcType=VARCHAR},
                #{item.wxyCode, jdbcType=VARCHAR},
                #{item.jscCode, jdbcType=VARCHAR},
                #{item.clzxCode, jdbcType=VARCHAR},
                #{item.fwdpCode, jdbcType=VARCHAR},
                #{item.gldpCode, jdbcType=VARCHAR},
                #{item.ywjfdpCode, jdbcType=VARCHAR},
                #{item.ictdpCode, jdbcType=VARCHAR},
                #{item.zwjlkbCode, jdbcType=VARCHAR},
                COALESCE(#{item.delFlg, jdbcType=CHAR}, '0')
            )
        </foreach>
        ON DUPLICATE KEY UPDATE
            cycle_id = VALUES(cycle_id),
            kpi_code = VALUES(kpi_code),
            code_hierarchy = VALUES(code_hierarchy),
            kpi_name = VALUES(kpi_name),
            kpi_desc = VALUES(kpi_desc),
            statement_desc = VALUES(statement_desc),
            art_statement = VALUES(art_statement),
            page_statement = VALUES(page_statement),
            unit = VALUES(unit),
            kpi_stats = VALUES(kpi_stats),
            kpi_resource = VALUES(kpi_resource),
            kpi_resource_detail = VALUES(kpi_resource_detail),
            kpi_resource_code = VALUES(kpi_resource_code),
            kpi_state = VALUES(kpi_state),
            domain_name = VALUES(domain_name),
            create_time = VALUES(create_time),
            lzgx_code = VALUES(lzgx_code),
            cx_code = VALUES(cx_code),
            gzdp_code = VALUES(gzdp_code),
            wxy_code = VALUES(wxy_code),
            jsc_code = VALUES(jsc_code),
            clzx_code = VALUES(clzx_code),
            fwdp_code = VALUES(fwdp_code),
            gldp_code = VALUES(gldp_code),
            ywjfdp_code = VALUES(ywjfdp_code),
            ictdp_code = VALUES(ictdp_code),
            zwjlkb_code = VALUES(zwjlkb_code),
            del_flg = VALUES(del_flg)
    </insert>
    -->
</mapper>
相关推荐
CRMEB1 小时前
电商项目中订单流程可以使用哪些设计模式?如何开发?
java·设计模式·gitee·开源·php·crmeb
CNAHYZ1 小时前
Apache HttpClient 配置 SSL 证书指南
java·spring boot·http
格鸰爱童话2 小时前
向AI学习项目技能(三)
java·人工智能·python·学习
weixin199701080162 小时前
南网商城商品详情页前端性能优化实战
java·前端·性能优化
iPadiPhone2 小时前
Spring Boot 自动装配原理与 Starter 开发实战
java·spring boot·后端·spring·面试
SuGarSJL2 小时前
FakeSMTP-2.1.1使用
java·maven
码匠君2 小时前
首个基于 Spring Boot 4 的正式版发布!Dante Cloud 4.X 新特性全解析
java·spring boot·后端
悟空码字2 小时前
SpringBoot + 百度地图SDK,打造企业级位置服务中台
java·百度·地图·编程技术·后端开发