基于java线程池和EasyExcel实现数据异步导入

基于java线程池和EasyExcel实现数据异步导入

2.代码实现

2.1 controller层

    @PostMapping("import")
    public void importExcel(MultipartFile file) throws IOException {
        importService.importExcelAsync(file);
    }

2.2 service层

@Resource
private SalariesListener salariesListener;

private ExecutorService executorService = Executors.newFixedThreadPool(20);

public void importExcelAsync(MultipartFile file) {
    // 开20个线程分别处理20个sheet
    List<Callable<Object>> tasks = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        int num = i;
        tasks.add(() -> {
            EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener)
                    .sheet(num).doRead();
            return null;
        });
    }

    try {
        //等待所有任务完成
        executorService.invokeAll(tasks);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

}

2.3实体

@Data
@TableName("salaries")
public class Salaries {
    private Integer empNo;
    private Integer salary;
    private Date fromDate;
    private Date toDate;
}

2.4easyExcel 监听

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.woniu.domain.Salaries;
import com.woniu.mapper.SalariesMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class SalariesListener extends ServiceImpl<SalariesMapper, Salaries> implements ReadListener<Salaries>, IService<Salaries> {

    private static final Log logger = LogFactory.getLog(SalariesListener.class);

    //创建一个线程池,用于异步保存数据
    private ExecutorService executorService = Executors.newFixedThreadPool(20);

    //创建一个线程安全的list,用于存储读取到的数据,使用ThreadLocal保证线程安全
    private ThreadLocal<ArrayList<Salaries>> salariesList = ThreadLocal.withInitial(ArrayList::new);

    //用于统计是第几次插入
    private static AtomicInteger count = new AtomicInteger(1);
    
    //设定需要异步批量插入的条数
    private static final int batchSize = 10000;

    @Resource
    private SalariesListener salariesListener;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invoke(Salaries data, AnalysisContext context) {
        //读取excel每一行的数据,添加到list中
        salariesList.get().add(data);
        //如果list的数据大于设定需要异步批量插入的条数,则执行异步插入
        if (salariesList.get().size() >= batchSize) {
            asyncSaveData();
        }
    }

    public void saveData() {
        if (!salariesList.get().isEmpty()) {
            saveBatch(salariesList.get(), salariesList.get().size());
            logger.info("第" + count.getAndAdd(1) + "次插入" + salariesList.get().size() + "条数据");
            salariesList.get().clear();
        }
    }

    public void asyncSaveData() {
        if (!salariesList.get().isEmpty()) {
            ArrayList<Salaries> salaries = (ArrayList<Salaries>) salariesList.get().clone();
            executorService.execute(new SaveTask(salaries, salariesListener));
            salariesList.get().clear();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doAfterAllAnalysed(AnalysisContext context) {
        logger.info("一个Sheet全部处理完");
        //考虑每个sheet批量插入数据的条数少于异步插入的条数
        asyncSaveData();
    }

    //创建一个线程类,用于异步保存数据
    static class SaveTask implements Runnable {

        private List<Salaries> salariesList;
        private SalariesListener salariesListener;

        public SaveTask(List<Salaries> salariesList, SalariesListener salariesListener) {
            this.salariesList = salariesList;
            this.salariesListener = salariesListener;
        }

        @Override
        public void run() {
            salariesListener.saveBatch(salariesList);
            //打印第几次插入,每次插入的数据
            logger.info("第" + count.getAndAdd(1) + "次插入" + salariesList.size() + "条数据");
        }
    }
}

2.5 建表语句

CREATE TABLE `salaries` (
  `emp_no` int(11) DEFAULT NULL COMMENT '员工号',
  `salary` int(11) DEFAULT NULL,
  `from_date` datetime DEFAULT NULL,
  `to_date` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
spring:
  servlet:
    multipart:
      max-request-size: 30MB
      max-file-size: 1024MB
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/llp?rewriteBatchedStatements=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
  main:
    allow-circular-references: true

3.测试验证

可以看到导入95万多条数据,耗时差不多在一份多钟

  • 导入开始时间

image-20250123102709029

  • 导入结束时间

image-20250123102727318

  • 入库数据

image-20250123102825151

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/960027.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信阅读网站小程序的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

【吉林乡镇界】面图层shp格式arcgis数据乡镇名称和编码wgs84无偏移内容测评

标题中的“吉林省乡镇界面图层shp格式arcgis数据乡镇名称和编码wgs84无偏移”揭示了这是一个地理信息系统&#xff08;GIS&#xff09;相关的数据集&#xff0c;主要用于描绘吉林省的乡镇边界。这个数据集包含了一系列的文件&#xff0c;它们是ArcGIS软件能够识别和处理的Shape…

71.在 Vue 3 中使用 OpenLayers 实现按住 Shift 拖拽、旋转和缩放效果

前言 在前端开发中&#xff0c;地图功能是一个常见的需求。OpenLayers 是一个强大的开源地图库&#xff0c;支持多种地图源和交互操作。本文将介绍如何在 Vue 3 中集成 OpenLayers&#xff0c;并实现按住 Shift 键拖拽、旋转和缩放地图的效果。 实现效果 按住 Shift 键&#…

Python NumPy(3):创建数组(2)

1 NumPy 从已有的数组创建数组 1.1 numpy.asarray numpy.asarray 类似 numpy.array&#xff0c;但 numpy.asarray 参数只有三个&#xff0c;比 numpy.array 少两个。 numpy.asarray(a, dtype None, order None) 参数描述a任意形式的输入参数&#xff0c;可以是&#xff0c…

qml Dialog详解

1、概述 Dialog是QML&#xff08;Qt Modeling Language&#xff09;中用于显示对话框的组件&#xff0c;它提供了一个模态窗口&#xff0c;通常用于与用户进行重要交互&#xff0c;如确认操作、输入信息或显示警告等。Dialog组件具有灵活的布局和样式选项&#xff0c;可以轻松…

二维数组一

目录 输出数组的第k行数输出数组的第k列数输出数组的每一行的和输出数组的每列的平均值最高成绩各个科目成绩的平均分求最大梯形的面积入门靶心数奇偶统计 输出数组的第k行数 题目描述 输入一个二维数组&#xff0c;显示他的第k行的值。 输入 第一行 n&#xff0c;m两个整数&…

单片机内存管理剖析

一、概述 在单片机系统中&#xff0c;内存资源通常是有限的&#xff0c;因此高效的内存管理至关重要。合理地分配和使用内存可以提高系统的性能和稳定性&#xff0c;避免内存泄漏和碎片化问题。单片机的内存主要包括程序存储器&#xff08;如 Flash&#xff09;和数据存储器&a…

计算机网络 (61)移动IP

前言 移动IP&#xff08;Mobile IP&#xff09;是由Internet工程任务小组&#xff08;Internet Engineering Task Force&#xff0c;IETF&#xff09;提出的一个协议&#xff0c;旨在解决移动设备在不同网络间切换时的通信问题&#xff0c;确保移动设备可以在离开原有网络或子网…

线性回归、协同过滤、基于内容过滤、主成分分析(PCA)

线性回归 使用item特征用户打分标签线性回归训练&#xff0c;最小化成本函数&#xff0c;得到每个用户的参数 协同过滤 协同过滤基于一个核心假设&#xff1a;相似的用户会有相似的兴趣&#xff0c;因此可以通过分析相似用户历史行为&#xff0c;来预测当前用户可能感兴趣的i…

引领产品创新: 2025 年 PM 效能倍增法则

本文讲述 PM 如何利用 AI 做到效率倍增&#xff0c;非常有借鉴意义&#xff0c;故而翻译于此。 原文链接&#xff1a;https://www.news.aakashg.com/p/the-ai-pms-playbook 在产品圈有一个广为流传的说法&#xff1a; “每个产品经理都应该成为 AI 产品经理。” 这个观点有一…

vscode无法格式化go代码的问题

CTRLshiftp 点击Go:Install/Update Tools 点击全选&#xff0c;OK&#xff01;

【外文原版书阅读】《机器学习前置知识》1.线性代数的重要性,初识向量以及向量加法

目录 ​编辑 ​编辑 1.Chapter 2 Why Linear Algebra? 2.Chapter 3 What Is a Vector? 个人主页&#xff1a;Icomi 大家好&#xff0c;我是Icomi&#xff0c;本专栏是我阅读外文原版书《Before Machine Learning》对于文章中我认为能够增进线性代数与机器学习之间的理解的…

对神经网络基础的理解

目录 一、《python神经网络编程》 二、一些粗浅的认识 1&#xff09; 神经网络也是一种拟合 2&#xff09;神经网络不是真的大脑 3&#xff09;网络构建需要反复迭代 三、数字图像识别的实现思路 1&#xff09;建立一个神经网络类 2&#xff09;权重更新的具体实现 3&am…

SOME/IP--协议英文原文讲解1

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 一、SOM…

移动光猫怎么自己改桥接模式?

环境&#xff1a; 型号H3-8s 问题描述&#xff1a; 家里宽带用的是H3-8s 光猫&#xff0c;想改桥接模式。 解决方案&#xff1a; 1.默认管理员账号和密码&#xff1a; 账号&#xff1a;CMCCAdmin 密码&#xff1a;aDm8H%MdAWEB页面我试了登陆不了&#xff0c;显示错误 …

2D 超声心动图视频到 3D 心脏形状重建的临床应用| 文献速递-医学影像人工智能进展

Title 题目 2D echocardiography video to 3D heart shape reconstruction for clinicalapplication 2D 超声心动图视频到 3D 心脏形状重建的临床应用 01 文献速递介绍 超声心动图是心血管医学中一种至关重要且广泛应用的影像学技术&#xff0c;利用超声波技术捕捉心脏及其…

web端ActiveMq测试工具

如何用vue3创建简单的web端ActiveMq测试工具&#xff1f; 1、复用vue3模板框架 创建main.js,引入APP文件&#xff0c;createApp创建文件&#xff0c;并加载element插件&#xff0c;然后挂载dom节点 2、配置vue.config.js脚本配置 mport { defineConfig } from "vite&qu…

STM32 GPIO配置 点亮LED灯

本次是基于STM32F407ZET6做一个GPIO配置&#xff0c;实现点灯实验。 新建文件 LED.c、LED.h文件&#xff0c;将其封装到Driver文件中。 双击Driver文件将LED.c添加进来 编写头文件&#xff0c;这里注意需要将Driver头文件声明一下。 在LED.c、main.c里面引入头文件LED.h LED初…

DroneXtract:一款针对无人机的网络安全数字取证工具

关于DroneXtract DroneXtract是一款使用 Golang 开发的适用于DJI无人机的综合数字取证套件&#xff0c;该工具可用于分析无人机传感器值和遥测数据、可视化无人机飞行地图、审计威胁活动以及提取多种文件格式中的相关数据。 功能介绍 DroneXtract 具有四个用于无人机取证和审…

用Python和PyQt5打造一个股票涨幅统计工具

在当今的金融市场中&#xff0c;股票数据的实时获取和分析是投资者和金融从业者的核心需求之一。无论是个人投资者还是专业机构&#xff0c;都需要一个高效的工具来帮助他们快速获取股票数据并进行分析。本文将带你一步步用Python和PyQt5打造一个股票涨幅统计工具&#xff0c;不…