多线程插入数据库

多线程插入数据库

package com.jd.adapter.processor.huarun.so;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;

/**
 * 华润数据批量插入助手类(基础版)
 * 实现了基本的多线程批量插入逻辑,但未处理日志TraceId透传,业务逻辑耦合在内部
 */
@Slf4j
@Component
public class HuaRunBatchInsertSimpleHelper {

    /**
     * 核心线程数
     */
    private static final int CORE_POOL_SIZE = 5;
    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = 10;
    /**
     * 队列容量
     */
    private static final int QUEUE_CAPACITY = 1000;
    /**
     * 线程空闲存活时间
     */
    private static final long KEEP_ALIVE_TIME = 60L;

    /**
     * 自定义线程池
     * 使用CallerRunsPolicy拒绝策略,防止任务丢失
     */
    private final ExecutorService executorService = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadFactory() {
                private int count = 0;
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "huarun-batch-insert-simple-" + (++count));
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    /**
     * 处理批量插入
     *
     * @param dataList 数据列表
     * @param batchSize 每批次大小
     * @param <T> 数据类型
     * @return 插入结果汇总
     */
    public <T> BatchInsertResult processBatchInsert(List<T> dataList, int batchSize) {
        if (CollectionUtils.isEmpty(dataList)) {
            return new BatchInsertResult(0, 0, Collections.emptyList());
        }

        // 1. 数据切分
        List<List<T>> partitions = partition(dataList, batchSize);
        List<CompletableFuture<BatchInsertResult>> futures = new ArrayList<>();

        // 2. 提交多线程任务
        for (List<T> batch : partitions) {
            CompletableFuture<BatchInsertResult> future = CompletableFuture.supplyAsync(
                    () -> doInsert(batch), 
                    executorService
            );
            futures.add(future);
        }

        // 3. 等待所有任务完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        try {
            // 设置超时时间,避免无限等待
            allFutures.get(1, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            log.error("批量插入任务被中断", e);
            Thread.currentThread().interrupt();
            return new BatchInsertResult(0, dataList.size(), Collections.singletonList("Task Interrupted"));
        } catch (ExecutionException | TimeoutException e) {
            log.error("批量插入任务执行异常或超时", e);
            // 继续向下执行,收集已完成的结果
        }

        // 4. 汇总结果
        return collectResults(futures);
    }

    /**
     * 执行具体的插入逻辑
     */
    private <T> BatchInsertResult doInsert(List<T> batch) {
        int success = 0;
        int fail = 0;
        List<String> errors = new ArrayList<>();

        try {
            // ---------------------------------------------------------
            // 【伪代码】数据库插入逻辑开始
            // 注意:这里没有处理TraceId,多线程下日志链路可能会断开
            // ---------------------------------------------------------
            
            // 示例:
            // if (CollectionUtils.isEmpty(batch)) return ...;
            // yourMapper.batchInsert(batch);
            
            // 模拟逻辑:假设插入成功
            success = batch.size();
            
            // ---------------------------------------------------------
            // 【伪代码】数据库插入逻辑结束
            // ---------------------------------------------------------
        } catch (Exception e) {
            log.error("本批次插入异常, size: {}", batch.size(), e);
            fail = batch.size();
            errors.add("Batch insert failed: " + e.getMessage());
        }

        return new BatchInsertResult(success, fail, errors);
    }

    /**
     * 汇总多线程执行结果
     */
    private BatchInsertResult collectResults(List<CompletableFuture<BatchInsertResult>> futures) {
        int totalSuccess = 0;
        int totalFail = 0;
        List<String> totalErrors = new ArrayList<>();

        for (CompletableFuture<BatchInsertResult> future : futures) {
            try {
                if (future.isDone() && !future.isCompletedExceptionally()) {
                    BatchInsertResult result = future.get();
                    totalSuccess += result.getSuccessCount();
                    totalFail += result.getFailCount();
                    if (!CollectionUtils.isEmpty(result.getErrorMessages())) {
                        totalErrors.addAll(result.getErrorMessages());
                    }
                } else {
                    // 对于异常或取消的任务,统计为失败
                    totalErrors.add("Task failed or was cancelled unexpectedly");
                }
            } catch (Exception e) {
                log.error("获取子任务结果异常", e);
                totalErrors.add("Get result exception: " + e.getMessage());
            }
        }
        return new BatchInsertResult(totalSuccess, totalFail, totalErrors);
    }

    /**
     * 辅助方法:切分List
     */
    private <T> List<List<T>> partition(List<T> list, int size) {
        if (list == null) return Collections.emptyList();
        List<List<T>> partitions = new ArrayList<>();
        for (int i = 0; i < list.size(); i += size) {
            partitions.add(new ArrayList<>(list.subList(i, Math.min(i + size, list.size()))));
        }
        return partitions;
    }

    /**
     * 容器销毁时优雅关闭线程池
     */
    @PreDestroy
    public void destroy() {
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
            }
        }
    }

    /**
     * 批量插入结果封装
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class BatchInsertResult {
        /**
         * 成功插入条数
         */
        private int successCount;
        /**
         * 失败条数
         */
        private int failCount;
        /**
         * 错误信息列表
         */
        private List<String> errorMessages;
    }
}

进阶版

package com.jd.adapter.processor.huarun.so;

import com.jd.adapter.util.ThreadLogUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;

/**
 * 华润数据批量插入助手类(进阶版)
 * 负责多线程批量处理数据入库,并处理跨线程日志链路追踪,支持自定义业务逻辑回调
 * @author liuyongchao32
 * @date 2026/1/8
 */
@Slf4j
@Component
public class HuaRunBatchInsertHelper {

    /**
     * 核心线程数
     */
    private static final int CORE_POOL_SIZE = 5;
    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = 10;
    /**
     * 队列容量
     */
    private static final int QUEUE_CAPACITY = 1000;
    /**
     * 线程空闲存活时间
     */
    private static final long KEEP_ALIVE_TIME = 60L;

    /**
     * 自定义线程池
     * 使用CallerRunsPolicy拒绝策略,防止任务丢失
     */
    private final ExecutorService executorService = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadFactory() {
                private int count = 0;
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "huarun-batch-insert-" + (++count));
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    /**
     * 处理批量插入,支持自定义业务逻辑
     *
     * @param dataList 数据列表
     * @param batchSize 每批次大小
     * @param batchConsumer 具体的批量插入业务逻辑
     * @param <T> 数据类型
     * @return 插入结果汇总
     */
    public <T> BatchInsertResult processBatchInsert(List<T> dataList, int batchSize, Consumer<List<T>> batchConsumer) {
        if (CollectionUtils.isEmpty(dataList)) {
            return new BatchInsertResult(0, 0, Collections.emptyList());
        }

        // 获取主线程的traceId
        String parentTraceId = ThreadLogUtil.getTid();

        // 1. 数据切分
        List<List<T>> partitions = partition(dataList, batchSize);
        List<CompletableFuture<BatchInsertResult>> futures = new ArrayList<>();

        // 2. 提交多线程任务
        for (List<T> batch : partitions) {
            CompletableFuture<BatchInsertResult> future = CompletableFuture.supplyAsync(
                    () -> {
                        // 子线程设置traceId,保证日志链路完整
                        ThreadLogUtil.requestStart(parentTraceId);
                        try {
                            return doInsert(batch, batchConsumer);
                        } finally {
                            // 清理ThreadLocal,防止内存泄漏或污染复用线程
                            ThreadLogUtil.requestEnd();
                        }
                    },
                    executorService
            );
            futures.add(future);
        }

        // 3. 等待所有任务完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        try {
            // 设置超时时间,避免无限等待,可根据实际业务调整
            allFutures.get(2, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            log.error("批量插入任务被中断", e);
            Thread.currentThread().interrupt();
            return new BatchInsertResult(0, dataList.size(), Collections.singletonList("Task Interrupted: " + e.getMessage()));
        } catch (ExecutionException | TimeoutException e) {
            log.error("批量插入任务执行异常或超时", e);
            // 这里不抛出异常,尝试收集已完成的结果,未完成的会在collectResults中标记
        }

        // 4. 汇总结果
        return collectResults(futures);
    }

    /**
     * 执行具体的插入逻辑
     */
    private <T> BatchInsertResult doInsert(List<T> batch, Consumer<List<T>> batchConsumer) {
        int success = 0;
        int fail = 0;
        List<String> errors = new ArrayList<>();

        try {
            // 执行传入的业务逻辑
            batchConsumer.accept(batch);
            success = batch.size();
        } catch (Exception e) {
            log.error("本批次插入异常, size: {}", batch.size(), e);
            fail = batch.size();
            errors.add("Batch insert failed: " + e.getMessage());
        }

        return new BatchInsertResult(success, fail, errors);
    }

    /**
     * 汇总多线程执行结果
     */
    private BatchInsertResult collectResults(List<CompletableFuture<BatchInsertResult>> futures) {
        int totalSuccess = 0;
        int totalFail = 0;
        List<String> totalErrors = new ArrayList<>();

        for (CompletableFuture<BatchInsertResult> future : futures) {
            try {
                // 只统计正常完成的任务,异常或超时的任务无法获取结果
                if (future.isDone() && !future.isCompletedExceptionally() && !future.isCancelled()) {
                    BatchInsertResult result = future.get(); // 此时get应立即返回
                    totalSuccess += result.getSuccessCount();
                    totalFail += result.getFailCount();
                    if (!CollectionUtils.isEmpty(result.getErrorMessages())) {
                        totalErrors.addAll(result.getErrorMessages());
                    }
                } else {
                    // 对于未完成(超时)或异常的任务,记录错误
                    totalErrors.add("Task failed, timed out or was cancelled");
                    // 注意:这里无法准确统计失败条数,因为不知道该future对应多少数据(除非在Future中携带元数据)
                    // 实际生产中可以考虑让Future返回更丰富的结果包含原始批次大小
                }
            } catch (Exception e) {
                log.error("获取子任务结果异常", e);
                totalErrors.add("Get result exception: " + e.getMessage());
            }
        }
        return new BatchInsertResult(totalSuccess, totalFail, totalErrors);
    }

    /**
     * 辅助方法:切分List
     */
    private <T> List<List<T>> partition(List<T> list, int size) {
        if (list == null) return Collections.emptyList();
        List<List<T>> partitions = new ArrayList<>();
        for (int i = 0; i < list.size(); i += size) {
            partitions.add(new ArrayList<>(list.subList(i, Math.min(i + size, list.size()))));
        }
        return partitions;
    }

    /**
     * 容器销毁时优雅关闭线程池
     */
    @PreDestroy
    public void destroy() {
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
            }
        }
    }

    /**
     * 批量插入结果封装
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class BatchInsertResult {
        /**
         * 成功插入条数
         */
        private int successCount;
        /**
         * 失败条数
         */
        private int failCount;
        /**
         * 错误信息列表
         */
        private List<String> errorMessages;
    }
}

在这里插入图片描述

多线程插入数据库时存在的问题及解决办法如下: ### 数据一致性问题 多线程同时插入数据可能会导致数据重复、数据冲突等一致性问题。例如,多个线程同时插入具有唯一约束的数据,就可能违反唯一约束条件。 解决办法:可以使用数据库的事务机制,将多个插入操作包裹在一个事务中,确保数据的一致性。另外,在代码层面可以添加同步机制,如使用锁来保证同一时间只有一个线程进行关键数据的插入操作。 ### 数据库连接池耗尽问题 如果线程数量过多,可能会导致数据库连接池中的连接被耗尽,从而使后续的插入操作无法获取连接而失败。 解决办法:合理配置线程池和数据库连接池的大小。根据数据库的性能和服务器的资源情况,设置合适的线程数和连接数。如引用[2]中配置线程池时,设置核心线程数、最大线程数和队列大小,避免线程数量过度增长。 ### 性能瓶颈问题 虽然多线程插入理论上可以提高插入性能,但如果数据库本身存在性能瓶颈,如磁盘I/O慢、索引过多等,多线程插入可能无法达到预期的效果,甚至会因为线程上下文切换开销过大而降低性能。 解决办法:对数据库进行性能优化,如优化数据库表结构、减少不必要的索引、使用批量插入语句等。同时,根据数据库的性能测试结果,调整线程池的参数。 ### 锁竞争问题 如果在多线程插入过程中使用了锁机制,可能会导致线程之间的锁竞争,从而降低并发性能。 解决办法:尽量减少锁的粒度,只在必要的代码段使用锁。可以使用更细粒度的锁,如使用分段锁或乐观锁来替代粗粒度的锁。 ### 代码示例 以下是综合引用内容,使用线程池进行多线程插入数据库的示例: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // 线程池配置 @Configuration @EnableAsync class ExecutorConfig { @Bean public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(400); executor.setThreadNamePrefix("thread-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } } // 插入数据的任务类 class ImportData implements Runnable { private static final Logger log = LoggerFactory.getLogger(ImportData.class); private List<OnlineConfirmExportVO> list; private CountDownLatch latch; private Integer i; private Integer agencyAgreementType; public ImportData(List<OnlineConfirmExportVO> list, CountDownLatch latch, Integer i, Integer agencyAgreementType) { this.list = list; this.latch = latch; this.i = i; this.agencyAgreementType = agencyAgreementType; } @Override public void run() { log.info("线程" + i + "开始执行,数据条数:" + list.size()); String errorImport = "导入模板不正确"; try { // 这里添加实际的插入数据库操作 log.info("线程" + i + "执行结束,数据条数:" + list.size()); } catch (Exception e) { log.error("Catch OnlineConfirm import Exception:", e); } finally { latch.countDown(); } } } class OnlineConfirmExportVO { // 类的具体实现 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值