1. MyBatis批量插入的典型场景与痛点在企业级应用开发中数据批量操作是绕不开的高频需求。以电商订单系统为例当用户完成购物车结算时往往需要同时生成订单主表记录和多个订单明细记录。如果采用单条SQL逐条插入的方式会产生严重的性能问题。我曾在实际项目中测试过插入1000条记录时单条提交耗时达到8秒而改用批量插入后仅需300毫秒。MyBatis作为Java生态中最流行的ORM框架提供了两种主流的批量处理方案一种是基于ExecutorType.BATCH的批处理模式另一种则是我们今天要重点探讨的动态SQL批量插入。后者特别适合在单次数据库交互中完成多条记录的原子性写入同时保持SQL语句的可读性和灵活性。2. 动态SQL批量插入的核心实现2.1 foreach标签的魔法MyBatis的foreach标签是动态SQL批量操作的核心武器。它的基础语法结构如下insert idbatchInsert parameterTypejava.util.List INSERT INTO table_name (column1, column2) VALUES foreach collectionlist itemitem separator, (#{item.property1}, #{item.property2}) /foreach /insert这里有几个关键点需要注意collection属性必须与Mapper接口参数名严格一致item定义了集合中单个元素的引用名称separator指定了每次循环后的分隔符实际开发中常见的坑当参数是List类型时collection属性值写list如果是数组则写array。这个细节在MyBatis官方文档中并不显眼但写错会导致报错Parameter list not found。2.2 不同数据库的适配策略不同数据库对批量SQL的语法支持存在差异需要针对性处理数据库类型批量语法特点适配方案MySQL支持多VALUES语法直接使用foreach生成多VALUESOracle不支持多VALUES使用UNION ALL或INSERT ALL语法PostgreSQL支持多VALUES但有长度限制分批处理或使用COPY命令SQL Server支持多VALUES但有参数数量限制结合BATCH模式或表变量方式对于Oracle这种特殊场景可以采用如下变通方案insert idbatchInsertOracle INSERT ALL foreach collectionlist itemitem INTO table_name (col1, col2) VALUES (#{item.val1}, #{item.val2}) /foreach SELECT 1 FROM DUAL /insert3. 性能优化与实战技巧3.1 批量大小的黄金分割点批量操作并非越大越好。根据我的压力测试数据基于MySQL 8.0批量大小耗时(ms)内存占用(MB)10012015500210451000350805000180032010000超时OOM风险建议将单次批量操作控制在500-1000条范围内超过这个阈值应该考虑分批处理。这里给出一个通用的分批工具类public class BatchUtil { public static T void batchHandle(ListT data, int batchSize, ConsumerListT consumer) { int total data.size(); for (int i 0; i total; i batchSize) { int end Math.min(i batchSize, total); ListT subList data.subList(i, end); consumer.accept(subList); } } } // 使用示例 BatchUtil.batchHandle(dataList, 500, mapper::batchInsert);3.2 主键冲突的优雅处理批量插入时经常会遇到主键或唯一键冲突以下是几种处理策略的对比忽略重复使用INSERT IGNOREMySQL或ON CONFLICT DO NOTHINGPostgreSQLinsert idbatchInsertIgnore INSERT IGNORE INTO table_name (...) VALUES (...) /insert覆盖更新使用ON DUPLICATE KEY UPDATEMySQL或ON CONFLICT DO UPDATEPostgreSQLinsert idbatchInsertUpdate INSERT INTO table_name (...) VALUES (...) ON DUPLICATE KEY UPDATE col1VALUES(col1) /insert先查后插对于复杂逻辑可以先查询已有记录再差异化插入4. 高级应用场景4.1 多表关联批量插入在订单-订单项这种典型的一对多关系中可以采用以下模式// 事务方法 Transactional public void createOrder(Order order, ListOrderItem items) { // 1. 插入主表 orderMapper.insert(order); // 2. 设置外键 items.forEach(item - item.setOrderId(order.getId())); // 3. 批量插入明细 orderItemMapper.batchInsert(items); }4.2 动态列处理当需要根据条件动态决定插入列时可以结合if标签实现insert idbatchInsertDynamic INSERT INTO user_data (user_id, if testlist[0].remark ! nullremark,/if create_time) VALUES foreach collectionlist itemitem separator, (#{item.userId}, if testitem.remark ! null#{item.remark},/if NOW()) /foreach /insert注意这里通过list[0]检查集合第一个元素的属性确保所有元素的动态列结构一致。5. 踩坑实录与排查指南5.1 经典错误参数绑定异常现象报错Parameter item not found原因在#{}中错误地使用了对象属性链错误示范!-- 错误写法 -- #{user.name}正确写法!-- 正确写法 -- #{item.user.name}5.2 SQL注入风险防范虽然MyBatis的#{}已经做了预编译处理但在动态SQL中仍然存在一些安全隐患避免${}拼接表名/列名必须使用时应该严格白名单校验批量删除时的IN子句应该使用foreach而非字符串拼接!-- 安全写法 -- WHERE id IN foreach collectionids itemid open( close) separator, #{id} /foreach5.3 事务失效场景以下情况会导致批量操作的事务失效方法自调用未通过代理异常被捕获未抛出使用了错误的传播机制如REQUIRES_NEW非public方法添加Transactional建议采用声明式事务管理Transactional(rollbackFor Exception.class) public void batchOperation(ListData list) { // 批量操作 }6. 性能对比测试在我的开发环境中MySQL 8.010000条数据不同批量方式的性能表现操作方式耗时(ms)内存峰值(MB)单条循环插入825060ExecutorType.BATCH120085动态SQL批量(500/批)65045动态SQL批量(1000/批)58050多值INSERTrewriteBatchedStatements42040关键发现JDBC的rewriteBatchedStatements参数对MySQL性能影响巨大合理的分批策略比单纯增大批量更有效动态SQL方式在内存占用上有明显优势在springboot配置中开启优化参数spring.datasource.urljdbc:mysql://localhost:3306/db?rewriteBatchedStatementstruecachePrepStmtstrueuseServerPrepStmtstrue7. 插件增强方案对于特别复杂的批量场景可以考虑以下增强方案MyBatis Plus的批量封装// 示例代码 ListUser userList ...; userService.saveBatch(userList, 1000);自定义批量处理器Component public class BatchProcessor { Autowired private SqlSessionFactory sqlSessionFactory; public T void executeBatch(ClassT mapperClass, ConsumerT consumer) { try(SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH)) { T mapper session.getMapper(mapperClass); consumer.accept(mapper); session.commit(); } } }存储过程调用对于超大批量数据可以考虑使用数据库存储过程我在实际项目中发现对于10万级以上的数据导入采用临时表存储过程的方式往往比应用层批量更高效。具体步骤是创建临时表使用LOAD DATA或批量INSERT导入临时表调用存储过程处理数据清理临时表这种方案将计算压力转移到数据库端避免了大量数据的网络传输。