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>
相关推荐
清华都得不到的好学生3 分钟前
数据结构->1.稀疏数组,2.数组队列(没有取模),3.环形队列
java·开发语言·数据结构
weyyhdke13 分钟前
基于SpringBoot和PostGIS的省域“地理难抵点(最纵深处)”检索及可视化实践
java·spring boot·spring
ILYT NCTR19 分钟前
【springboot】Spring 官方抛弃了 Java 8!新idea如何创建java8项目
java·spring boot·spring
weixin_4250230020 分钟前
PG JSONB 对应 Java 字段 + MyBatis-Plus 完整实战
java·开发语言·mybatis
不早睡不改名@1 小时前
Netty源码分析---Reactor线程模型深度解析(二)
java·网络·笔记·学习·netty
子非鱼@Itfuture1 小时前
`<T> T execute(...)` 泛型方法 VS `TaskExecutor<T>` 泛型接口对比分析
java·开发语言
2601_949816161 小时前
spring.profiles.active和spring.profiles.include的使用及区别说明
java·后端·spring
疯狂成瘾者1 小时前
接口规范设计:返回体 + 错误码 + 异常处理
java·状态模式
阿Y加油吧1 小时前
LeetCode 二叉搜索树双神题通关!有序数组转平衡 BST + 验证 BST,小白递归一把梭
java·算法·leetcode
项目帮1 小时前
Java毕设选题推荐:基于springboot区块链的电子病历数据共享平台设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
java·spring boot·课程设计