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>
相关推荐
破土士V3 分钟前
【Java基础语法10】继承、多态、抽象类接口、字符串与异常等
java·开发语言
轻刀快马4 分钟前
撕开 Spring 的底裤:解析 Bean 生命周期与三级缓存的“破局”之术
java·spring·缓存
KobeSacre6 分钟前
JVM ZGC
java·开发语言·jvm
Chase_______21 分钟前
【Java基础 | 13】IO 流(下):缓冲流、转换流、序列化与综合案例
java·开发语言
bush439 分钟前
嵌入式linux学习记录十二,mmap
java·linux·学习
源码宝43 分钟前
基于SpringCloud+UniApp的智慧工地云平台整体架构设计与实现
java·人工智能·spring cloud·源码·智慧工地·云平台
天文家1 小时前
深入理解装饰器与适配器:从设计模式到 Spring AOP 的工程实践
java·设计模式
贺国亚1 小时前
Spring-AI与LangChain4j
java·人工智能·spring
野生技术架构师2 小时前
2026 Java面试宝典(春招/社招/秋招通用):没有前言,只有答案,直接开背
java·开发语言·面试
mN9B2uk172 小时前
数据库的约束简介
java·数据库·sql