百万级excel导入导出

news2025/1/16 14:02:10

项目目录结构:
在这里插入图片描述
*

导入思路:

excel拆分为多个sheet,开启20个线程分别处理20个sheet,采用批量插入的方式入库
*

导出思路:

开启20个线程分页读取数据,放入到map中,CountDownLatch保证并发安全,将数据写入到excel中导出,easyExcel不支持多线程并发导出,只能单线程导出
*

@Data
@TableName("aaa")
public class Salaries extends Model {

    private String empNo;

    private String salary;

    private String formDate;

    private String toDate;
}
@Mapper
public interface SalariesMapper extends BaseMapper<Salaries> {

    @Select("select id,emp_no,salary,form_date,to_date from aaa limit #{offSet},#{size}")
    public List<Salaries> page(@Param("offSet") Integer offSet, @Param("size") Integer size);
}
@Service
public class ImportService {


    @Resource
    private SalariesListener salariesListener;

    private ExecutorService executorService = Executors.newFixedThreadPool(20);

    public void importExcel(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), Salaries.class,salariesListener).doReadAll();
    }

    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) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

}

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

    private ExecutorService executorService = Executors.newFixedThreadPool(20);

    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) {
        // 第一种方案:单条插入
        // saveOne(data);
        salariesList.get().add(data);
        if (salariesList.get().size() >= batchSize) {
            saveData();
            // asyncSaveData();
        }
    }

    public void saveData() {
        if (!salariesList.get().isEmpty()) {
            saveBatch(salariesList.get(), salariesList.get().size());
            log.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));
        }
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("一个Sheet全部处理完");
        if (salariesList.get().size() >= batchSize) {
            saveData();
        }
    }


    public void saveOne(Salaries data){
        save(data);
        log.info("第"+"次插入1条数据");
    }


    /**
     * 方案1:单条插入
     * 方案2:批量插入,放入到List中,>10000条数据时插入,并将list清空,从threadLocal中清空
     *
     *
     * 方案3:
     */

    static class SaveTask implements Runnable {
        private List<Salaries> salariesList;
        private SalariesListener salariesListener;

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

        @Override
        public void run() {
            salariesListener.saveBatch(salariesList);
            log.info("第"+count.getAndAdd(1) + "次插入" + salariesList.size() + "条数据");
        }
    }

}
@Service
public class ExportService {

    public static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    @Resource
    private SalariesMapper salariesMapper;

    public void exportExcel(HttpServletResponse response) throws IOException {
        setExportHeader(response);
        // 方案1:全量查询,写一个sheet中
        List<Salaries> salaries = salariesMapper.selectList(null);
        EasyExcel.write(response.getOutputStream(),Salaries.class).sheet().doWrite(salaries);
    }

    public void exportExcel2(HttpServletResponse response) throws IOException {
        setExportHeader(response);
        // 方案2:全量查询,写多个sheet中
        List<Salaries> salaries = salariesMapper.selectList(null);
        // EasyExcel.write(response.getOutputStream(),Salaries.class).sheet().doWrite(salaries);
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(),Salaries.class).build()) {
            WriteSheet writeSheet1 = EasyExcel.writerSheet(1,"模板1").build();
            WriteSheet writeSheet2 = EasyExcel.writerSheet(2,"模板2").build();
            WriteSheet writeSheet3 = EasyExcel.writerSheet(3,"模板3").build();

            List<Salaries> data1 = salaries.subList(0, salaries.size() / 3);
            List<Salaries> data2 = salaries.subList(salaries.size() / 3, salaries.size() * 2 / 3);
            List<Salaries> data3 = salaries.subList(salaries.size() * 2 / 3, salaries.size());

            excelWriter.write(data1, writeSheet1);
            excelWriter.write(data2, writeSheet2);
            excelWriter.write(data3, writeSheet3);
        }
    }

    public void exportExcel3(HttpServletResponse response) throws IOException {
        setExportHeader(response);
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(),Salaries.class).build()) {
            Long count = salariesMapper.selectCount(null);
            Integer pages = 10;
            Long size = count /pages;
            for (int i = 0; i < pages; i++) {
                WriteSheet writeSheet = EasyExcel.writerSheet(i,"模板" + i).build();
                Page<Salaries> page = new Page<>();
                page.setCurrent(i+1);
                page.setSize(size);
                Page<Salaries> selectPage = salariesMapper.selectPage(page,null);
                excelWriter.write(selectPage.getRecords(),writeSheet);
            }
        }
    }


    public void exportExcel4(HttpServletResponse response) throws InterruptedException, IOException {
        setExportHeader(response);
        Long count = salariesMapper.selectCount(null);

        Integer pages = 20;
        Integer size = count.intValue() / pages;
        ExecutorService executorService = Executors.newFixedThreadPool(pages);
        CountDownLatch countDownLatch = new CountDownLatch(pages);

        //Map<Integer,Page<Salaries>> pageMap = new HashMap<>();
        Map<Integer,List<Salaries>> pageMap = new HashMap<>();
        for (int i = 0; i < pages; i++) {
            int finalI = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
//                    Page<Salaries> page = new Page<>();
//                    page.setCurrent(finalI + 1);
//                    page.setSize(size);
//                    Page<Salaries> selectPage = salariesMapper.selectPage(page,null);
                    List<Salaries> list = salariesMapper.page(finalI*size,size);
                    pageMap.put(finalI,list);
                    countDownLatch.countDown();
                }
            });
        }

        countDownLatch.await();

        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), Salaries.class).build()) {
            for(Map.Entry<Integer,List<Salaries>> entry : pageMap.entrySet()) {
                Integer num = entry.getKey();
                List<Salaries> salariesPage = entry.getValue();
                WriteSheet writeSheet = EasyExcel.writerSheet(num,"模板"+num).build();
                excelWriter.write(salariesPage,writeSheet);
            }
        }
    }


    private static void setExportHeader(HttpServletResponse response) {
        response.setContentType(CONTENT_TYPE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + "zhouyu.xlsx");
    }
}

程序运行结果,导入时间花费247s,导出时间花费43s
在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++(14)——vector

目录 vector是什么&#xff1f; vector的使用 vector的构造 vector iterator的使用 vector空间增长问题 vector的增删查改 push_back和pop_back operator[] 总结 vector是什么&#xff1f; vector是什么呢&#xff1f;按照英文来说&#xff0c;vector的英文是向量、矢…

Lord 3DMCV7-AHRS 时间同步硬件触发设置

目的:通过FPGA发送脉冲触发IMU采集数据。FPGA发送脉冲时,IMU才有数据产生。 FPGA与IMU的硬件接线就不讲了,这里主要说明的是IMU的设置以及ROS驱动的config文件更改。 1. WIN上位机设置 通过IMU在WINDOWS的上位机SensorConnect对IMU的GPIO、波特率等基本功能进行设值,具体…

视频素材软件app免费网站哪里找?

在如今的短视频时代&#xff0c;为了满足视频素材创作者们的需求&#xff0c;有很多提供免费短视频素材下载的应用程序可供选择。 接下来&#xff0c;我们将对视频素材软件app免费网站哪里找做出介绍 可以考虑以下几个视频素材软件app免费 蛙学网 提供多元化视频&#xff0c…

面试常问:你在项目中遇到了哪些比较棘手的问题?怎么解决的?

你在项目中遇到了哪些比较棘手的问题?怎么解决的&#xff1f;这个问题是面试官经常会问的一个问题。 如果你回答我在项目中没有怎么遇到&#xff0c;那么面试官会觉得你什么都不会&#xff0c;对项目了解也不够深入也没有负责什么项目。 面试官其实还挺关心的是应聘者的问题…

注意力机制Attention、CA注意力机制

一、注意力机制 产生背景&#xff1a; 大数据时代&#xff0c;有很多数据提供给我们。对于人来说&#xff0c;可以利用重要的数据&#xff0c;过滤掉不重要的数据。那对于模型来说&#xff08;CNN、LSTM&#xff09;&#xff0c;很难决定什么重要、什么不重要&#xff0c;因此…

APP开发如何成功上架到应用市场?

在当今移动互联网时代&#xff0c;开发一款优秀的APP并将其成功上架到各大应用市场是每个开发者梦寐以求的成就之一。但是&#xff0c;不同类型的APP在上架过程中可能会面临各种不同的挑战和要求。 APP上架到应用市场一般包括以下几个流程&#xff1a; 步骤一&#xff1a;开发及…

web部署 三

案例: 1/在其中一个网站目录下创建\software文件夹&#xff0c;里面放txtppt/mp4/iso,文件类型。 2/web站点目录浏览启动应用。 3/用win10客户机浏览software目录下文件&#xff0c;并下载。txtppt/mp4/iso&#xff0c;发现问题并解决。 首先我们先建立一个software的文件夹并…

Linux控制台、终端、Shell 的历史

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系&#xff01; ①微思网络&#xff0c;始于2002年&#xff01;专注IT认证培训22年。 ② 领取学习资料/课程咨询&#xff1a;小美老师&#xff08;wx&#xff09;&…

工作中Git如何切换远程仓库地址

工作中Git如何切换远程仓库地址 部门之前的仓库不用了&#xff0c;重新建了一个仓库&#xff0c;但是上传代码还是上传到了之前的仓库里面了&#xff0c;所以得进行修改&#xff0c;下面将修改地址的方法进行操作。 方法一、直接修改远程仓库地址 查看当前远程仓库地址 git …

【图形界面】学生宿舍信息管理系统,简单,模板框架,含完整代码

目录 开发一个学生宿舍管理系统 概述 开发环境 程序设计 功能展示 主菜单 添加学生信息界面 删除学生信息界面 修改学生信息界面 查询学生信息界面 5. 完整代码 6. 总结 开发一个学生宿舍管理系统 在本文中&#xff0c;我们将介绍如何使用Python和Tkinter库开发一…

P1149 [NOIP2008 提高组] 火柴棒等式

题目描述 给你 &#xfffd;n 根火柴棍&#xff0c;你可以拼出多少个形如 &#xfffd;&#xfffd;&#xfffd;ABC 的等式&#xff1f;等式中的 &#xfffd;A、&#xfffd;B、&#xfffd;C 是用火柴棍拼出的整数&#xff08;若该数非零&#xff0c;则最高位不能是 00&…

【MySQL】4. 表的操作

表的操作 1. 创建表 语法&#xff1a; CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎;说明&#xff1a; field 表示列名 datatype 表示列的类型 character set 字符集&#xff0c…

iOS面试题锦集

1. 问&#xff1a;一张图片所占内存大小跟什么有关&#xff1f; 图片所占内存大小&#xff0c;与图片的宽高有关 我们平时看到的png、jpg、webp这些图片格式&#xff0c;其实都是图片压缩格式。通过对应的算法来优化了大小以节省网络传输与本地保存所需的资源。 但是当我们加…

Nuxt3配置本地访问链接 -- Network: use --host to expose

这种表示没有在线访问本地链接 在nuxt.config.ts里面 export default defineNuxtConfig({// 配置本地访问链接devServer: {host: 192.168.1.41,//自己电脑的Ipport: 3000,}, }) 复制到浏览器就可以访问&#xff0c;给别人使用必须是局域网&#xff0c;同一个网络

FreeRTOS学习第10篇--队列使用示例

FreeRTOS学习第10篇–队列使用示例 本文目标&#xff1a;FreeRTOS学习第10篇–队列使用示例 按照本文的描述&#xff0c;可以进行简单的使用队列。 本文实验条件&#xff1a;拥有C语言基础&#xff0c;装有编译和集成的开发环境&#xff0c;比如&#xff1a;Keil uVision5 …

独家授权!广东盈世获网易邮箱反垃圾服务的独家授权,邮件反垃圾更全面

近日&#xff0c;广东盈世计算机科技有限公司&#xff08;以下简称“Coremail”&#xff09;成功获得了网易&#xff08;杭州&#xff09;网络有限公司&#xff08;以下简称“网易”&#xff09;授予的网易邮箱反垃圾服务独家使用权。这一授权使得Coremail能够在邮件安全产品上…

创建出色用户界面的五个关键技巧,助您实现用户体验的提升!

易于理解的用户界面通常很容易阅读&#xff0c;它们有美丽的布局和完美的颜色组合&#xff0c;通过提供简单和直观的选项&#xff0c;告诉用户应用程序可以做什么。 因此&#xff0c;在创建用户界面时&#xff0c;&#xff08;UI&#xff09;当时&#xff0c;UI设计师往往优先…

人大金仓大小写敏感处理

人大金仓安装的时候&#xff0c;不管是否选择大小写敏感&#xff1b;查询的时候加和不加双引号&#xff0c;查询出来的都是小写 针对人大金仓大小写&#xff0c;我们实际引用全是大写的情况&#xff0c;解决方案如下 添加配置&#xff0c;将查询结果全都转成大写 1、本地打开…

FastAPI 是什么?深入解析

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建基于 Python 的 API。它是一个开源项目&#xff0c;基于 Starlette 和 Pydantic 库构建而成&#xff0c;提供了强大的功能和高效的性能。 FastAPI 官网地址&#xff1a;fastapi.tiango…

npm WARN config init.license Use `--init-license` instead.

背景&#xff1a;近期升级本地node版本&#xff08;v14.15.4 --> v18.19.1&#xff09;&#xff0c;npm -v报警 npm WARN config init.license Use --init-license instead. npm WARN config init.author.name Use --init-author-name instead. 分析&#xff1a; npm高版本…