性能炸裂!Spring Boot 3.4 + ThreadPoolTaskExecutor 批量插入百万数据!

75 阅读2分钟

点击上方“程序员蜗牛g”,选择“设为星标”跟蜗牛哥一起,每天进步一点点程序员蜗牛g大厂程序员一枚 跟蜗牛一起 每天进步一点点31篇原创内容**公众号

利用 Spring Boot 3.4 结合 ThreadPoolTaskExecutor,使数据插入任务并发执行,提高数据库写入吞吐量。

**线程池配置
**

图片

Spring 线程池 Bean 配

package com.icoderoad.config;
import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;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.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;
@Configuration@EnableAsync@Slf4jpublic class ExecutorConfig {    @Value("${async.executor.thread.core_pool_size}")    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")    private String namePrefix;
    @Bean(name = "asyncServiceExecutor")    public Executor asyncServiceExecutor() {        log.warn("启动线程池 asyncServiceExecutor");        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(corePoolSize);        executor.setMaxPoolSize(maxPoolSize);        executor.setQueueCapacity(queueCapacity);        executor.setThreadNamePrefix(namePrefix);        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        executor.initialize();        return executor;    }}

异步任务执行

package com.icoderoad.service.impl;
import com.icoderoad.mapper.LogOutputResultMapper;import com.icoderoad.model.LogOutputResult;import com.icoderoad.service.AsyncService;import lombok.extern.slf4j.Slf4j;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;
import java.util.List;import java.util.concurrent.CountDownLatch;
@Slf4j@Servicepublic class AsyncServiceImpl implements AsyncService {    @Override    @Async("asyncServiceExecutor")    public void executeAsync(List<LogOutputResult> logOutputResults, LogOutputResultMapper logOutputResultMapper, CountDownLatch countDownLatch) {        try {            log.warn("执行异步插入任务");            logOutputResultMapper.addLogOutputResultBatch(logOutputResults);        } finally {            countDownLatch.countDown();        }    }}

业务调用多线程插入

package com.icoderoad.service.impl;
import com.icoderoad.mapper.LogOutputResultMapper;import com.icoderoad.model.LogOutputResult;import com.icoderoad.service.AsyncService;import com.icoderoad.utils.ConvertHandler;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;
import java.util.List;import java.util.concurrent.CountDownLatch;
@Slf4j@Servicepublic class LogOutputService {    private final AsyncService asyncService;    private final LogOutputResultMapper logOutputResultMapper;
    public LogOutputService(AsyncService asyncService, LogOutputResultMapper logOutputResultMapper) {        this.asyncService = asyncService;        this.logOutputResultMapper = logOutputResultMapper;    }
    public int testMultiThread() {        List<LogOutputResult> logOutputResults = getTestData();        List<List<LogOutputResult>> lists = ConvertHandler.splitList(logOutputResults, 100);        CountDownLatch countDownLatch = new CountDownLatch(lists.size());
        for (List<LogOutputResult> listSub : lists) {            asyncService.executeAsync(listSub, logOutputResultMapper, countDownLatch);        }
        try {            countDownLatch.await();        } catch (Exception e) {            log.error("多线程插入异常: " + e.getMessage());        }
        return logOutputResults.size();    }
    private List<LogOutputResult> getTestData() {        return ConvertHandler.generateTestData(3000000);    }}

工具类 ConvertHandler

package com.icoderoad.utils;
import com.icoderoad.model.LogOutputResult;
import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;import java.util.stream.IntStream;
public class ConvertHandler {    public static <T> List<List<T>> splitList(List<T> list, int size) {        List<List<T>> parts = new ArrayList<>();        for (int i = 0; i < list.size(); i += size) {            parts.add(new ArrayList<>(list.subList(i, Math.min(list.size(), i + size))));        }        return parts;    }
    public static List<LogOutputResult> generateTestData(int count) {        return IntStream.range(0, count)                .mapToObj(i -> new LogOutputResult((long) i, "TestLog " + i))                .collect(Collectors.toList());    }}

数据访问层 LogOutputResultMapper

package com.icoderoad.mapper;
import com.icoderoad.model.LogOutputResult;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import java.util.List;
@Mapperpublic interface LogOutputResultMapper {    @Insert("INSERT INTO log_output_result (id, message) VALUES (#{id}, #{message})")    void addLogOutputResultBatch(List<LogOutputResult> logOutputResults);}

测试结果

  • 单线程 插入 300万 数据,耗时 5.75分钟
  • 30个线程 并发插入 300万 数据,耗时 1.67分钟,效率提升 3.4倍
  • 数据完整性检查无误,无重复数据。

结论

图片图片

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!