利用ThreadPoolTaskExecutor多线程大数据插入

开发目的

大批量数据导入到mysql数据库

1.创建线程池
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;

/**
 * @author yt
 * @create 2023/2/15 14:26
 */
@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {

    @Value("${executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${executor.thread.prefix}")
    private String namePrefix;

    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.warn("startasyncServiceExecutor");
        //在这里修改
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}
2.线程池参数配置文件 application.yml
executor:
  thread:
    #配置核心线程数
    core_pool_size: 5
    #配置最大线程数
    max_pool_size: 10
    #配置队列大小
    queue_capacity: 100
    #配置线程池中的线程的名称前缀
    prefix: mx
3.线程池创建完毕

首先mybatis-plus中默认提供了一个批量保存数据到数据库的方法saveBatch(),批处理实质上还是一条条的sql去执行,但是它做了预编译优化,只编译一次sql,但是还是一个for循环,一条执行一次,数据量多的时候,效率也不见得很好。所以这里使用mybatis-plus注入器插件

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;

import java.util.List;


/**
 * 原来自带的是属于for循环插入,这里使用的是foreach
 * @author 于涛
 */
public class EasySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        // 注意:此SQL注入器继承了DefaultSqlInjector(默认注入器),调用了DefaultSqlInjector的getMethodList方法,保留了mybatis-plus的自带方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
        return methodList;
    }
}
4.将mybatisplus注入器注册到bean
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author admin
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

    @Bean
    public EasySqlInjector easySqlInjector () {
        return new EasySqlInjector();
    }
}
5.编写批量插入方法,其他mapper继承EasyBaseMapper即可使用
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.Collection;

public interface EasyBaseMapper<T> extends BaseMapper<T> {
    /**
     * 批量插入 仅适用于mysql
     *
     * @param entityList 实体列表
     * @return 影响行数
     */
    Integer insertBatchSomeColumn(Collection<T> entityList);
}
6.创建异步接口和实现类
import com.kaying.luck.pojo.file.dos.FileDO;

import java.util.List;

/**
 * @author yt
 * @create 2023/2/15 15:04
 */
public interface AsyncService {
    /**
     * 批量插入文件信息
     * @param fileDOList
     */
    void fileBatchInsert(List<FileDO> fileDOList);
}

@Async("asyncServiceExecutor")注解就是将异步使用多线程来执行操作,调用几次就是开启几个线程,如果超过了最大线程就可以进入线程池队列等待

import com.kaying.luck.mapper.file.FileMapper;
import com.kaying.luck.pojo.file.dos.FileDO;
import com.kaying.luck.service.async.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author yt
 * @create 2023/2/15 15:05
 */
@Service
public class AsyncServiceImpl implements AsyncService {

    @Autowired
    private FileMapper fileMapper;

    @Async("asyncServiceExecutor")
    @Override
    public void fileBatchInsert(List<FileDO> fileDOList) {
        fileMapper.insertBatchSomeColumn(fileDOList);
    }

}
7.模拟2000条数据批量插入,如果是1000条以内就单线程,否则开启多线程批量插入
import com.kaying.luck.pojo.file.dos.FileDO;
import com.kaying.luck.service.async.AsyncService;
import com.kaying.luck.service.file.FileTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @author yt
 * @create 2023/2/13 13:37
 */
@Service
public class FileTaskServiceImpl implements FileTaskService {

    private static final Logger logger = LoggerFactory.getLogger(FileTaskService.class);

    //每个线程处理的数据量
    private static final int deviceCount = 1000;

    @Autowired
    private AsyncService asyncService;

    @Override
    public void test() {

        List<FileDO> fileDOList = new ArrayList<>();

        for (int i = 0; i < 2000; i++) {
            FileDO fileDO = new FileDO();
            fileDO.setActivityNo("DD "+i);
            fileDOList.add(fileDO);
        }

        /* 批量下发 */
        try {
            if (fileDOList.size() <= deviceCount) {
                /* 异步处理 */
                asyncService.fileBatchInsert(fileDOList);
            } else {
                List<List<FileDO>> li = createList(fileDOList, deviceCount);
                for (List<FileDO> liop : li) {
                    /* 异步处理 */
                    asyncService.fileBatchInsert(liop);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.toString() + " 错误所在行数:" + e.getStackTrace()[0].getLineNumber());
        }
    }

    /**
     * 数据拆分
     *
     * @param targe 集合
     * @param size 多少条数据拆分成一个数组
     * @return
     */
    public List<List<FileDO>> createList(List<FileDO> targe, int size) {
        List<List<FileDO>> listArr = new ArrayList<List<FileDO>>();
        //获取被拆分的数组个数
        int arrSize = targe.size() % size == 0 ? targe.size() / size : targe.size() / size + 1;
        for (int i = 0; i < arrSize; i++) {
            List<FileDO> sub = new ArrayList<FileDO>();
            //把指定索引数据放入到list中
            for (int j = i * size; j <= size * (i + 1) - 1; j++) {
                if (j <= targe.size() - 1) {
                    sub.add(targe.get(j));
                }
            }
            listArr.add(sub);
        }
        return listArr;
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中可以使用ThreadPoolTaskExecutor来实现多线程处理批量数据入库的操作。ThreadPoolTaskExecutorSpring框架提供的一个线程池实现类,可以方便地管理线程池的创建和销毁,并提供了一些配置参数来控制线程池的行为。 下面是使用ThreadPoolTaskExecutor实现多线程处理批量数据入库的步骤: 1. 首先,需要在项目中引入Spring的依赖,以使用ThreadPoolTaskExecutor类。可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> ``` 2. 在代码中创建一个ThreadPoolTaskExecutor对象,并进行相关配置。可以通过在Spring配置文件中配置bean,或者使用Java代码进行配置。以下是一个示例配置: ```java @Configuration @EnableAsync public class ThreadPoolConfig { @Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); // 设置核心线程数 executor.setMaxPoolSize(20); // 设置最大线程数 executor.setQueueCapacity(100); // 设置队列容量 executor.setThreadNamePrefix("MyThread-"); // 设置线程名前缀 executor.initialize(); // 初始化线程池 return executor; } } ``` 3. 在需要进行批量数据入库的地方,使用@Async注解标记方法,并指定使用的线程池。例如: ```java @Service public class DataBatchService { @Autowired private ThreadPoolTaskExecutor taskExecutor; @Async("taskExecutor") public void processBatchData(List<Data> dataList) { // 批量数据入库的逻辑处理 // ... } } ``` 4. 调用processBatchData方法时,会自动使用线程池中的线程进行处理。例如: ```java @Autowired private DataBatchService dataBatchService; public void batchDataInsert(List<Data> dataList) { dataBatchService.processBatchData(dataList); } ``` 这样就可以利用ThreadPoolTaskExecutor实现多线程处理批量数据入库了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值