记录于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)需要拿到失败数据做补偿
 *List<PkAlarmEventEntity> 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(IService<T> service, List<T> dataList, String entityName) {
        batchInsert(service, dataList, entityName, DEFAULT_BATCH_SIZE);
    }

    /**
     * 通用批量插入方法,失败批次数据将以 JSON 格式记录到 ERROR 日志,便于后续手动导入
     */
    public static <T> void batchInsert(IService<T> service, List<T> 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 (List<T> 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 {
                    List<T> 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(
            IService<T> service,
            List<T> dataList,
            String entityName,
            int batchSize) {
        if (dataList == null || dataList.isEmpty()) {
            return true; // 空数据视为成功
        }

        batchSize = Math.max(1, batchSize);
        boolean allSuccess = true;
        int batchNumber = 1;

        for (List<T> batchList : Lists.partition(dataList, batchSize)) {
            try {
                service.saveBatch(batchList);
            } catch (Exception e) {
                allSuccess = false;
                log.error("【批量插入失败】实体: {}, 批次: {}, 数据量: {}, 异常: {}",
                        entityName, batchNumber, batchList.size(), e.getMessage(), e);

                try {
                    List<T> 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> List<T> batchInsertAndReturnFailures(
            IService<T> service,
            List<T> dataList,
            String entityName,
            int batchSize) {
        if (dataList == null || dataList.isEmpty()) {
            return Collections.emptyList();
        }

        batchSize = Math.max(1, batchSize);
        List<T> failedRecords = new ArrayList<>();
        int batchNumber = 1;

        for (List<T> 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 {
                    List<T> 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(IService<T> service, List<T> dataList, String entityName) {
        return batchInsertAndReturnSuccess(service, dataList, entityName, DEFAULT_BATCH_SIZE);
    }

    public static <T> List<T> batchInsertAndReturnFailures(IService<T> service, List<T> dataList, String entityName) {
        return batchInsertAndReturnFailures(service, dataList, entityName, DEFAULT_BATCH_SIZE);
    }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐