2025-04-28
Mysql
0

目录

MyBatis批量新增或更新数据的高效实现方案
一、业务场景分析
二、实现方案比较
1. 逐条处理方式
2. MyBatis批量语句方式
方案一:使用foreach拼接多条SQL
方案二:使用CASE WHEN语法实现真正批量更新
3. ON DUPLICATE KEY UPDATE方式
4. MyBatis批处理执行器方式
三、MyBatis-Plus的便捷实现
1. saveOrUpdateBatch方法
2. 自定义SQL注入器
四、性能优化建议
五、方案对比总结
六、结论

MyBatis批量新增或更新数据的高效实现方案

在实际开发中,我们经常会遇到需要批量处理数据的场景,特别是需要根据某些条件判断是插入新数据还是更新已有数据的情况。本文将详细介绍基于MyBatis实现批量新增或更新的几种高效方案。

一、业务场景分析

批量新增或更新(通常称为"upsert")的典型场景包括:

  • 数据同步:从外部系统同步数据到本地数据库
  • 日志处理:批量处理大量日志记录
  • 报表统计:定时更新统计指标
  • 缓存刷新:批量更新缓存数据

这类操作的核心需求是:根据某些字段判断数据是否存在,存在则更新,不存在则插入

二、实现方案比较

1. 逐条处理方式

实现方式:在Java代码中循环遍历集合,对每条数据单独执行查询,然后根据查询结果决定执行插入或更新。

java
public void updateBatch(List<MyData> datas){ for(MyData data : datas){ try{ MyData existing = myDataDao.selectById(data.getId()); if(existing != null){ myDataDao.update(data); }else{ myDataDao.insert(data); } }catch(Exception e){ // 异常处理 } } }

优缺点分析

  • 优点:实现简单,逻辑清晰,出错只影响单条数据
  • 缺点:性能差,每次操作都需要连接数据库,数据量大时效率低下

2. MyBatis批量语句方式

方案一:使用foreach拼接多条SQL

实现方式:通过MyBatis的foreach标签拼接多条update语句,需要MySQL连接参数添加allowMultiQueries=true

xml
<update id="updateBatch" parameterType="java.util.List"> <foreach collection="list" item="item" separator=";"> update course <set> name=${item.name} </set> where id = ${item.id} </foreach> </update>

优缺点分析

  • 优点:减少网络IO次数
  • 缺点:每条记录update一次,性能仍然不理想,容易造成阻塞

方案二:使用CASE WHEN语法实现真正批量更新

实现方式:利用SQL的CASE WHEN语法实现单条SQL批量更新多条数据

xml
<update id="updateBatch" parameterType="java.util.List"> update mydata_table set status= <foreach collection="list" item="item" index="index" separator=" " open="case ID" close="end"> when #{item.id} then #{item.status} </foreach> where id in <foreach collection="list" index="index" item="item" separator="," open="(" close=")"> #{item.id,jdbcType=BIGINT} </foreach> </update>

注意事项

  • 必须添加where条件,否则会更新全部数据
  • 可以同时更新多个字段
xml
<update id="updateChartParamByAccountAndChartid" parameterType="list"> update followme_parameters <trim prefix="set" suffixOverrides=","> <trim prefix="signal_source =case" suffix="end,"> <foreach collection="list" item="item" index="index"> <if test="item.signalSource!=null"> when account=#{item.account} and chart_id=#{item.chartId} then #{item.signalSource} </if> </foreach> </trim> <trim prefix="rate =case" suffix="end,"> <foreach collection="list" item="item" index="index"> <if test="item.rate!=null"> when account=#{item.account} and chart_id=#{item.chartId} then #{item.rate} </if> </foreach> </trim> </trim> where id in <foreach collection="list" item="item" index="index" separator="," open="(" close=")"> #{item.id} </foreach> </update>

3. ON DUPLICATE KEY UPDATE方式

实现方式:利用MySQL特有的ON DUPLICATE KEY UPDATE语法实现插入或更新

xml
<insert id="batchInsertOrUpdate"> INSERT INTO hh_adx_monitor_summary (code, plan_id, cons, exp, conv, click, dimension_time) VALUES <foreach collection="list" item="item" separator=","> (#{item.code}, #{item.planId}, #{item.cons}, #{item.exp}, #{item.conv}, #{item.click}, #{item.dimensionTime}) </foreach> ON DUPLICATE KEY UPDATE code = VALUES(code), plan_id = VALUES(plan_id), cons = VALUES(cons), exp = VALUES(exp), conv = VALUES(conv), click = VALUES(click), dimension_time = VALUES(dimension_time) </insert>

前提条件

  • 表必须有唯一索引或主键
  • 在MySQL中,这种方式的性能通常是最好的

4. MyBatis批处理执行器方式

实现方式:使用MyBatis的BATCH执行器,配合标准insert和update语句

java
@Test public void testInsertWithMapper() { try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) { Mapper userMapper = sqlSession.getMapper(Mapper.class); User user1 = new User(null, "Pocoyo"); userMapper.insert(user1); User user2 = new User(null, "Valentina"); userMapper.insert(user2); sqlSession.flushStatements(); sqlSession.commit(); } }

特点

  • 需要手动管理事务
  • 性能介于逐条处理和真正批量处理之间
  • 兼容性最好,适合多数据库环境

三、MyBatis-Plus的便捷实现

如果你使用MyBatis-Plus,它提供了更简便的方式:

1. saveOrUpdateBatch方法

java
// 服务接口继承IService public interface IHhChainCustomerInfoService extends IService<HhChainCustomerInfo> {} // 服务实现类继承ServiceImpl public class HhChainCustomerInfoServiceImpl extends ServiceImpl<HhChainCustomerInfoMapper, HhChainCustomerInfo> implements IHhChainCustomerInfoService {} // 使用示例 List<HhChainCustomerInfo> list = ...; hhChainCustomerInfoService.saveOrUpdateBatch(list);

注意事项

  • 如果实体类继承了基础类(如BaseEntity),可能会有问题
  • 底层实现仍然是查询后判断插入或更新,性能不如真正的批量操作

2. 自定义SQL注入器

对于开启逻辑删除的场景,可以通过自定义SQL注入器实现不带逻辑删除的批量操作

java
public class SelectListWithoutLogicDelete extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { // 实现省略... } @Override protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) { // 重写去掉逻辑删除相关代码 } }

四、性能优化建议

  1. 批量大小控制:每批次处理500-1000条数据,避免单次操作数据量过大
  2. 索引优化:确保用于判断数据是否存在的字段有合适索引
  3. 连接池配置:适当增大连接池大小,避免批量操作耗尽连接
  4. 事务控制:合理设置事务边界,避免长事务
  5. 多线程处理:对大数据量可以考虑分片多线程处理

五、方案对比总结

方案性能复杂度兼容性适用场景
逐条处理数据量小,逻辑复杂
foreach多条SQL中(需数据库支持)中等数据量
CASE WHEN批量大数据量,MySQL
ON DUPLICATE KEY低(MySQL特有)MySQL,大数据量
MyBatis批处理多数据库环境
MyBatis-Plus快速开发,中小数据量

六、结论

对于MySQL数据库,ON DUPLICATE KEY UPDATE方式通常是性能最好的选择,但需要注意它只适用于MySQL。如果需要兼容多种数据库,CASE WHEN批量更新MyBatis批处理执行器是更好的选择。对于简单场景或中小数据量,使用MyBatis-Plus的saveOrUpdateBatch可以大大提高开发效率。

无论选择哪种方案,都应该根据实际业务场景、数据量和性能要求进行选择,并在测试环境中验证其性能和正确性。