记录于2025-12-10 个人博客现转录CSDN基于 MyBatis-Plus 封装的批量插入高实用性工具设计上兼顾了易用性、健壮性、可观测性和扩展性。#特点场景全覆盖三种返回值形态适配 “无感知执行、结果判断、失败补偿” 三类核心业务场景高可运维性分级日志、失败数据序列化、性能指标监控让生产环境的问题可定位、可恢复健壮且易用空值防护、批次大小校验、泛型封装、清晰注释降低接入成本和线上故障风险。package com.zznode.jiake.e2e.commons.mybatis.utils.dmlUtil; import com.baomidou.mybatisplus.extension.service.IService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; /*** * * * 批量插入工具类 * * * 说明 mybatisplus 支撑的批量插入工具如特别要求可重写 IService saveBatch 方法 * *1)只关心是否成功boolean * boolean ok BatchInsertUtils.batchInsertAndReturnSuccess( * pkAlarmEventService, listEnd, 异常下线告警); * if (!ok) { * log.warn(存在插入失败请检查日志); * } * *2)需要拿到失败数据做补偿 *ListPkAlarmEventEntity failures BatchInsertUtils.batchInsertAndReturnFailures( * pkAlarmEventService, listEnd, 异常下线告警); * * if (!failures.isEmpty()) { * // 例如存入失败表、发消息队列、写文件等 * alarmCompensationService.saveFailedAlarms(failures); * } * * *3)void 无须返回任何值 * * BatchInsertUtils.batchInsert(pkAlarmEventService, listEnd, 异常下线告警); * * */ public class BatchInsertUtils { private static final Logger log LoggerFactory.getLogger(BatchInsertUtils.class); private static final ObjectMapper objectMapper new ObjectMapper(); private static final int DEFAULT_BATCH_SIZE 3000; // 原有方法无返回值保持不变 /** * 通用批量插入方法失败批次数据将以 JSON 格式记录到 ERROR 日志便于后续手动导入 * * param service MyBatis-Plus 的 IService 实现 * param dataList 待插入数据列表 * param entityName 实体名称用于日志描述 * param T 实体类型需支持 JSON 序列化 * * * author lz * email noway * date 2025-11-18 13:47:05 */ public static T void batchInsert(IServiceT service, ListT dataList, String entityName) { batchInsert(service, dataList, entityName, DEFAULT_BATCH_SIZE); } /** * 通用批量插入方法失败批次数据将以 JSON 格式记录到 ERROR 日志便于后续手动导入 */ public static T void batchInsert(IServiceT service, ListT dataList, String entityName, int batchSize) { if (dataList null || dataList.isEmpty()) { log.info(【{}】数据为空跳过批量插入, entityName); return; } batchSize Math.max(1, batchSize); log.info(合计插入 【{}】表数据{} 条批次大小{}, entityName, dataList.size(), batchSize); long startTime System.currentTimeMillis(); int totalInsertedRecords 0; int batchNumber 1; for (ListT batchList : Lists.partition(dataList, batchSize)) { log.debug(执行第 {} 次批量入库操作处理 【{}】数据 {} 条, batchNumber, entityName, batchList.size()); try { service.saveBatch(batchList); totalInsertedRecords batchList.size(); } catch (Exception e) { log.error(【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}, entityName, batchNumber, batchList.size(), e.getMessage(), e); try { ListT logData batchList.size() 100 ? batchList.subList(0, 100) : new ArrayList(batchList); // 避免视图问题 String failedDataJson objectMapper.writeValueAsString(logData); log.error(【手动导入备用数据 - {} - 批次{}】(共{}条仅展示前{})\n{}, entityName, batchNumber, batchList.size(), logData.size(), failedDataJson); } catch (JsonProcessingException jsonEx) { log.error(【警告】失败批次数据无法序列化为 JSON, jsonEx); log.error(【Fallback 数据预览】前5条: {}, batchList.subList(0, Math.min(5, batchList.size()))); } } batchNumber; } long endTime System.currentTimeMillis(); log.info(【{}】数据插入完毕成功插入{} 条耗时{} 毫秒, entityName, totalInsertedRecords, (endTime - startTime)); } // 新增方法 1返回 boolean是否全部成功 /** * 批量插入并返回是否全部成功无任何异常 */ public static T boolean batchInsertAndReturnSuccess( IServiceT service, ListT dataList, String entityName, int batchSize) { if (dataList null || dataList.isEmpty()) { return true; // 空数据视为成功 } batchSize Math.max(1, batchSize); boolean allSuccess true; int batchNumber 1; for (ListT batchList : Lists.partition(dataList, batchSize)) { try { service.saveBatch(batchList); } catch (Exception e) { allSuccess false; log.error(【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}, entityName, batchNumber, batchList.size(), e.getMessage(), e); try { ListT logData batchList.size() 100 ? batchList.subList(0, 100) : new ArrayList(batchList); String failedDataJson objectMapper.writeValueAsString(logData); log.error(【手动导入备用数据 - {} - 批次{}】(共{}条仅展示前{})\n{}, entityName, batchNumber, batchList.size(), logData.size(), failedDataJson); } catch (JsonProcessingException jsonEx) { log.error(【警告】失败批次数据无法序列化为 JSON, jsonEx); } } batchNumber; } return allSuccess; } // 新增方法 2返回失败的数据列表 /** * 批量插入并返回所有失败的记录可用于补偿处理 * * return 失败的记录列表不可变若全部成功则返回空列表 */ public static T ListT batchInsertAndReturnFailures( IServiceT service, ListT dataList, String entityName, int batchSize) { if (dataList null || dataList.isEmpty()) { return Collections.emptyList(); } batchSize Math.max(1, batchSize); ListT failedRecords new ArrayList(); int batchNumber 1; for (ListT batchList : Lists.partition(dataList, batchSize)) { try { service.saveBatch(batchList); } catch (Exception e) { // 收集失败数据 failedRecords.addAll(new ArrayList(batchList)); // 防止 sublist 视图问题 log.error(【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}, entityName, batchNumber, batchList.size(), e.getMessage(), e); try { ListT logData batchList.size() 100 ? batchList.subList(0, 100) : new ArrayList(batchList); String failedDataJson objectMapper.writeValueAsString(logData); log.error(【手动导入备用数据 - {} - 批次{}】(共{}条仅展示前{})\n{}, entityName, batchNumber, batchList.size(), logData.size(), failedDataJson); } catch (JsonProcessingException jsonEx) { log.error(【警告】失败批次数据无法序列化为 JSON, jsonEx); } } batchNumber; } // 返回不可变列表 return Collections.unmodifiableList(failedRecords); } // 便捷重载使用默认批次大小 public static T boolean batchInsertAndReturnSuccess(IServiceT service, ListT dataList, String entityName) { return batchInsertAndReturnSuccess(service, dataList, entityName, DEFAULT_BATCH_SIZE); } public static T ListT batchInsertAndReturnFailures(IServiceT service, ListT dataList, String entityName) { return batchInsertAndReturnFailures(service, dataList, entityName, DEFAULT_BATCH_SIZE); } }
基于 MyBatis-Plus 批量插入工具
发布时间:2026/6/29 12:08:14
记录于2025-12-10 个人博客现转录CSDN基于 MyBatis-Plus 封装的批量插入高实用性工具设计上兼顾了易用性、健壮性、可观测性和扩展性。#特点场景全覆盖三种返回值形态适配 “无感知执行、结果判断、失败补偿” 三类核心业务场景高可运维性分级日志、失败数据序列化、性能指标监控让生产环境的问题可定位、可恢复健壮且易用空值防护、批次大小校验、泛型封装、清晰注释降低接入成本和线上故障风险。package com.zznode.jiake.e2e.commons.mybatis.utils.dmlUtil; import com.baomidou.mybatisplus.extension.service.IService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; /*** * * * 批量插入工具类 * * * 说明 mybatisplus 支撑的批量插入工具如特别要求可重写 IService saveBatch 方法 * *1)只关心是否成功boolean * boolean ok BatchInsertUtils.batchInsertAndReturnSuccess( * pkAlarmEventService, listEnd, 异常下线告警); * if (!ok) { * log.warn(存在插入失败请检查日志); * } * *2)需要拿到失败数据做补偿 *ListPkAlarmEventEntity failures BatchInsertUtils.batchInsertAndReturnFailures( * pkAlarmEventService, listEnd, 异常下线告警); * * if (!failures.isEmpty()) { * // 例如存入失败表、发消息队列、写文件等 * alarmCompensationService.saveFailedAlarms(failures); * } * * *3)void 无须返回任何值 * * BatchInsertUtils.batchInsert(pkAlarmEventService, listEnd, 异常下线告警); * * */ public class BatchInsertUtils { private static final Logger log LoggerFactory.getLogger(BatchInsertUtils.class); private static final ObjectMapper objectMapper new ObjectMapper(); private static final int DEFAULT_BATCH_SIZE 3000; // 原有方法无返回值保持不变 /** * 通用批量插入方法失败批次数据将以 JSON 格式记录到 ERROR 日志便于后续手动导入 * * param service MyBatis-Plus 的 IService 实现 * param dataList 待插入数据列表 * param entityName 实体名称用于日志描述 * param T 实体类型需支持 JSON 序列化 * * * author lz * email noway * date 2025-11-18 13:47:05 */ public static T void batchInsert(IServiceT service, ListT dataList, String entityName) { batchInsert(service, dataList, entityName, DEFAULT_BATCH_SIZE); } /** * 通用批量插入方法失败批次数据将以 JSON 格式记录到 ERROR 日志便于后续手动导入 */ public static T void batchInsert(IServiceT service, ListT dataList, String entityName, int batchSize) { if (dataList null || dataList.isEmpty()) { log.info(【{}】数据为空跳过批量插入, entityName); return; } batchSize Math.max(1, batchSize); log.info(合计插入 【{}】表数据{} 条批次大小{}, entityName, dataList.size(), batchSize); long startTime System.currentTimeMillis(); int totalInsertedRecords 0; int batchNumber 1; for (ListT batchList : Lists.partition(dataList, batchSize)) { log.debug(执行第 {} 次批量入库操作处理 【{}】数据 {} 条, batchNumber, entityName, batchList.size()); try { service.saveBatch(batchList); totalInsertedRecords batchList.size(); } catch (Exception e) { log.error(【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}, entityName, batchNumber, batchList.size(), e.getMessage(), e); try { ListT logData batchList.size() 100 ? batchList.subList(0, 100) : new ArrayList(batchList); // 避免视图问题 String failedDataJson objectMapper.writeValueAsString(logData); log.error(【手动导入备用数据 - {} - 批次{}】(共{}条仅展示前{})\n{}, entityName, batchNumber, batchList.size(), logData.size(), failedDataJson); } catch (JsonProcessingException jsonEx) { log.error(【警告】失败批次数据无法序列化为 JSON, jsonEx); log.error(【Fallback 数据预览】前5条: {}, batchList.subList(0, Math.min(5, batchList.size()))); } } batchNumber; } long endTime System.currentTimeMillis(); log.info(【{}】数据插入完毕成功插入{} 条耗时{} 毫秒, entityName, totalInsertedRecords, (endTime - startTime)); } // 新增方法 1返回 boolean是否全部成功 /** * 批量插入并返回是否全部成功无任何异常 */ public static T boolean batchInsertAndReturnSuccess( IServiceT service, ListT dataList, String entityName, int batchSize) { if (dataList null || dataList.isEmpty()) { return true; // 空数据视为成功 } batchSize Math.max(1, batchSize); boolean allSuccess true; int batchNumber 1; for (ListT batchList : Lists.partition(dataList, batchSize)) { try { service.saveBatch(batchList); } catch (Exception e) { allSuccess false; log.error(【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}, entityName, batchNumber, batchList.size(), e.getMessage(), e); try { ListT logData batchList.size() 100 ? batchList.subList(0, 100) : new ArrayList(batchList); String failedDataJson objectMapper.writeValueAsString(logData); log.error(【手动导入备用数据 - {} - 批次{}】(共{}条仅展示前{})\n{}, entityName, batchNumber, batchList.size(), logData.size(), failedDataJson); } catch (JsonProcessingException jsonEx) { log.error(【警告】失败批次数据无法序列化为 JSON, jsonEx); } } batchNumber; } return allSuccess; } // 新增方法 2返回失败的数据列表 /** * 批量插入并返回所有失败的记录可用于补偿处理 * * return 失败的记录列表不可变若全部成功则返回空列表 */ public static T ListT batchInsertAndReturnFailures( IServiceT service, ListT dataList, String entityName, int batchSize) { if (dataList null || dataList.isEmpty()) { return Collections.emptyList(); } batchSize Math.max(1, batchSize); ListT failedRecords new ArrayList(); int batchNumber 1; for (ListT batchList : Lists.partition(dataList, batchSize)) { try { service.saveBatch(batchList); } catch (Exception e) { // 收集失败数据 failedRecords.addAll(new ArrayList(batchList)); // 防止 sublist 视图问题 log.error(【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}, entityName, batchNumber, batchList.size(), e.getMessage(), e); try { ListT logData batchList.size() 100 ? batchList.subList(0, 100) : new ArrayList(batchList); String failedDataJson objectMapper.writeValueAsString(logData); log.error(【手动导入备用数据 - {} - 批次{}】(共{}条仅展示前{})\n{}, entityName, batchNumber, batchList.size(), logData.size(), failedDataJson); } catch (JsonProcessingException jsonEx) { log.error(【警告】失败批次数据无法序列化为 JSON, jsonEx); } } batchNumber; } // 返回不可变列表 return Collections.unmodifiableList(failedRecords); } // 便捷重载使用默认批次大小 public static T boolean batchInsertAndReturnSuccess(IServiceT service, ListT dataList, String entityName) { return batchInsertAndReturnSuccess(service, dataList, entityName, DEFAULT_BATCH_SIZE); } public static T ListT batchInsertAndReturnFailures(IServiceT service, ListT dataList, String entityName) { return batchInsertAndReturnFailures(service, dataList, entityName, DEFAULT_BATCH_SIZE); } }