在 MyBatis 开发中,处理 SQL 特殊符号(如 >、<)是基础必知内容,而面对大量数据的批量插入与更新,如何编写高效且规范的 XML 映射文件则是进阶关键。本文将分两部分进行详解。
第一部分:MyBatis 中大于、小于、等于的写法
在 XML 文件中,< 和 > 等符号会被解析器误认为是标签的开始或结束,因此必须进行转义或使用 CDATA 块。
方法一:实体字符转义(推荐用于简单比较)
将特殊符号替换为对应的 HTML 实体字符。这是最通用的方法,适用于所有 XML 解析环境。
表格
| 原符号 | 含义 | 替换符号 (Entity) |
|---|---|---|
< |
小于 | < |
<= |
小于等于 | <= |
> |
大于 | > |
>= |
大于等于 | >= |
& |
和 | & |
' |
单引号 | ' |
" |
双引号 | " |
代码示例:
<!-- 查询创建时间在 startTime 和 endTime 之间的数据 -->
<select id="selectByTimeRange" resultType="com.example.Entity">
SELECT * FROM table_name
WHERE create_date_time >= #{startTime}
AND create_date_time <= #{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 映射文件实现
✅ 优化点说明:
- TRUNCATE vs DELETE :清空表建议使用
TRUNCATE TABLE,速度更快且重置自增主键(若无需触发器)。 - IFNULL 的处理 :原代码大量使用
IFNULL(..., '')。这会将数据库中的NULL强制转为空字符串。请确认业务需求 :如果数据库字段允许NULL且需要区分"无值"和"空值",建议去掉IFNULL,直接让 MyBatis 处理null。下文保留原逻辑以符合你的业务现状,但增加了jdbcType规范。 - 批量更新的性能陷阱 :原代码使用
<foreach separator=";">拼接多条UPDATE语句。- 风险 :默认 MySQL JDBC 驱动禁止一次执行多条 SQL,需在连接串配置
allowMultiQueries=true。 - 性能:交互次数虽少,但解析开销大。
- 优化方案 :下文提供了两种方案 。方案 A 保留你的原逻辑(需配置驱动);方案 B 推荐使用 MySQL 特有的
ON DUPLICATE KEY UPDATE语法(性能更优,无需特殊配置,但需表有唯一索引/主键)。
- 风险 :默认 MySQL JDBC 驱动禁止一次执行多条 SQL,需在连接串配置
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>