Spring Boot整合EasyExcel:实现大规模数据的并行导出与压缩下载

news2024/10/30 9:34:05

SpringBoot集成EasyExcel 3.x: 高效实现Excel数据的优雅导入与导出

一、描述

在 Spring Boot 应用中,整合 EasyExcel 实现并行导出数据并进行 Zip 压缩下载可以极大地提高数据处理效率和用户体验。以下是详细描述及结合代码的示例:

1、EasyExcel 简介

EasyExcel 是一个 Java 操作 Excel 的开源工具,它能以简单的方式读写大型 Excel 文件,并且性能高效、内存占用低。

2、并行导出的优势

在处理大量数据导出时,传统的单线程导出方式可能会非常耗时,导致用户等待时间过长。而并行导出可以充分利用多核处理器的优势,将数据分成多个部分同时进行处理,从而大大提高导出速度。

3、Zip 压缩下载的作用

当导出的数据量较大时,直接下载可能会导致网络传输缓慢或者出现问题。通过将导出的 Excel 文件进行 Zip 压缩,可以减小文件大小,提高下载速度,并且方便用户管理和存储。

二、案例

1、添加依赖

   <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>easyexcel</artifactId>
       <version>3.1.1</version>
   </dependency>

    <!-- 简化实体类的get,set操作 --> 
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
    </dependency>


    <!-- Commons IO(用于压缩文件操作) -->    
    <dependency>        
        <groupId>commons-io</groupId>        
        <artifactId>commons-io</artifactId>        
        <version>2.11.0</version>    
    </dependency>

2、准备数据模型

创建一个用于存储数据的实体类:

@Data
public class Order {
    
    private Long id;    
    private String productName;    
    private Integer quantity;    
    private BigDecimal price;    

}

3、使用 EasyExcel 导出 Excel 文件

定义了一个名为ExcelExportUtil 的工具类,其中包含一个静态方法writeToExcel。

这个方法的主要作用是将一个订单列表(List<Order>)中的数据写入到指定路径的 Excel 文件中。它使用了阿里巴巴的 EasyExcel 库来实现这个功能。

import com.alibaba.excel.EasyExcel;
import java.io.File;
import java.util.List;

public class ExcelExportUtil {
    public static void writeToExcel(List<Order> orders, String filePath) {
        try {
            // 使用 EasyExcel 进行 Excel 文件写入操作
            // 指定输出文件路径 filePath、数据类型 Order.class 和工作表名称 "订单数据"
            EasyExcel.write(filePath, Order.class)
                   .sheet("订单数据")
                   .doWrite(orders);
        } catch (Exception e) {
            // 如果在写入过程中出现异常,打印异常信息
            e.printStackTrace();
        }
    }
}

4、实现并行导出逻辑

import java.util.List;

public interface ExportService {

    // 并行导出订单的方法
    void exportOrdersInParallel(List<List<Order>> ordersList, String outputDir);

}

实现ExportService接口,这段代码实现了并行导出订单的功能。它使用了 Java 的CompletableFuture和自定义的线程池来同时处理多个订单列表的导出任务

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

@Service
public class ExportServiceImpl implements ExportService{

    // 创建一个固定大小为 10 的线程池
    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    // 并行导出订单的方法
    public void exportOrdersInParallel(List<List<Order>> ordersList, String outputDir) {
        // 创建一个 CompletableFuture 数组来存储每个任务的 Future 对象
        CompletableFuture<Void>[] futures = new CompletableFuture[ordersList.size()];

        // 遍历订单列表,为每个子列表创建一个异步任务
        IntStream.range(0, ordersList.size()).forEach(index -> {
            List<Order> orders = ordersList.get(index);
            futures[index] = CompletableFuture.runAsync(() -> {
                // 生成文件名
                String fileName = "订单_" + Thread.currentThread().getId() + ".xlsx";
                try {
                    // 调用工具方法将订单写入 Excel 文件
                    ExcelExportUtil.writeOrdersToExcel(orders, outputDir + fileName);
                } catch (Exception e) {
                    // 如果出现异常,打印异常信息
                    e.printStackTrace();
                }
            }, executor);
        });

        // 等待所有任务完成
        CompletableFuture.allOf(futures).join();
    }
}

以下是对代码的简单解释:

  1. 创建一个固定大小为 10 的线程池Executors,用于执行异步任务。

  2. exportOrdersInParallel方法接受一个订单列表和输出目录作为参数。

  3. 创建一个CompletableFuture<Void>[]数组来存储每个异步任务的 Future 对象。

  4. 使用IntStream.range遍历订单列表的索引,为每个订单子列表创建一个异步任务。

  5. 在异步任务中,生成文件名,然后尝试调用ExcelExportUtil.writeOrdersToExcel方法将订单写入 Excel 文件。如果出现异常,打印异常信息。

  6. 最后,使用CompletableFuture.allOf等待所有异步任务完成。

请注意,这段代码假设存在一个Order类和一个ExcelExportUtil工具类,其中包含了将订单写入 Excel 文件的方法

5、压缩文件为 zip

完成 Excel 文件的导出后,我们需要将这些文件压缩成一个 zip 文件。

5.1 使用 ZipOutputStream

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipUtil {
    public static void zipFiles(String sourceDir, String zipFile) throws IOException {
        // 创建输出的 ZIP 文件流
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zipOut = new ZipOutputStream(fos)) {

            // 获取要压缩的源目录下的文件列表
            File fileToZip = new File(sourceDir);
            if (fileToZip.isDirectory()) {
                for (File file : fileToZip.listFiles()) {
                    if (file.isFile()) {
                        try (FileInputStream fis = new FileInputStream(file)) {
                            // 创建 ZIP 条目,表示要添加到 ZIP 文件中的文件
                            ZipEntry zipEntry = new ZipEntry(file.getName());
                            zipOut.putNextEntry(zipEntry);

                            byte[] bytes = new byte[1024];
                            int length;
                            // 循环读取文件内容并写入 ZIP 文件
                            while ((length = fis.read(bytes)) >= 0) {
                                zipOut.write(bytes, 0, length);
                            }
                        }
                    }
                }
            }
        }
    }
}

以下是对代码的详细解释:

  1. zipFiles方法接受两个参数:sourceDir表示要压缩的源目录路径,zipFile表示输出的 ZIP 文件路径。

  2. 在方法内部,首先创建了一个FileOutputStream和一个ZipOutputStream,用于写入 ZIP 文件。

  3. 然后获取源目录下的文件列表。如果源目录是一个文件夹,则遍历其中的文件。

  4. 对于每个文件,创建一个FileInputStream来读取文件内容。

  5. 创建一个ZipEntry,表示要添加到 ZIP 文件中的文件条目,条目名称为文件的名称。

  6. 将ZipEntry添加到ZipOutputStream中。

  7. 使用一个循环,每次读取 1024 字节的数据,并将其写入到 ZIP 文件中。

  8. 最后,关闭所有的输入流和输出流。

请注意,这段代码假设源目录中只包含文件,不包含子文件夹。如果需要递归压缩子文件夹中的文件,可以对代码进行进一步的扩展

6. 实现下载功能

在 Spring Boot 中,可以通过 HTTP 响应的形式将生成的 zip 文件提供给前端下载。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FileDownloadController {
    @GetMapping("/downloadZip")
    public ResponseEntity<InputStreamResource> downloadZip() throws IOException {
        // 定义源目录路径,这里假设是 /tmp/excel_files
        String sourceDir = "/tmp/excel_files";
        // 定义生成的 ZIP 文件路径,这里假设是 /tmp/orders.zip
        String zipFilePath = "/tmp/orders.zip";

        try {
            // 调用 ZipUtil 类的方法来压缩源目录中的文件到指定的 ZIP 文件
            ZipUtil.zipFiles(sourceDir, zipFilePath);

            // 创建一个 File 对象表示要下载的 ZIP 文件
            File file = new File(zipFilePath);

            // 创建一个 InputStreamResource 对象,从文件输入流中读取数据
            InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

            // 设置 HTTP 响应头信息
            HttpHeaders headers = new HttpHeaders();
            // 设置内容处置头,指示浏览器下载文件,并指定文件名
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName());
            // 设置内容类型为 application/octet-stream,表示二进制数据
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            // 设置内容长度,以便浏览器正确显示下载进度
            headers.setContentLength(file.length());

            // 返回一个包含 InputStreamResource 的 ResponseEntity 对象,
            // 表示成功的 HTTP 响应,包含要下载的文件数据和设置好的响应头
            return ResponseEntity.ok()
                   .headers(headers)
                   .body(resource);
        } catch (IOException e) {
            // 如果在压缩文件或读取文件过程中出现 IOException,返回内部服务器错误响应
            return ResponseEntity.status(500).build();
        } finally {
            // 在方法结束时,无论是否发生异常,尝试删除生成的 ZIP 文件
            File zipFile = new File(zipFilePath);
            if (zipFile.exists()) {
                zipFile.delete();
            }
        }
    }
}

7. 完整的业务流程

  1. 数据分批处理:假设我们需要导出上百万条订单数据,为了高效管理和处理,首先需要根据用户ID、订单日期或其他相关条件将庞大的数据集进行分片。每片数据将被独立处理并导出到不同的 Excel 文件中,这样可以有效减少单次处理的数据量,避免内存溢出等问题。

  2. 并行处理:为了进一步提升导出效率,我们可以利用 Java 的 CompletableFuture 框架来并行处理各个数据片段。通过为每个数据片段分配一个独立的导出任务,并让这些任务在多个线程上同时执行,可以确保多个 Excel 文件能够同时生成,从而显著加快整体处理速度。

  3. 文件压缩:在所有 Excel 文件成功生成后,我们需要将这些文件整合到一个压缩包中以便于传输和存储。这时,可以使用 ZipOutputStream 类来创建一个 zip 文件,并将所有生成的 Excel 文件逐一添加到这个 zip 文件中进行压缩。这样做不仅可以减少文件占用的空间,还能提高文件传输的效率。

  4. 提供下载:为了让用户能够方便地获取压缩后的数据包,我们需要在前端提供一个下载链接。当用户点击该链接时,服务器会将压缩包发送给用户的浏览器进行下载。为了确保下载过程的安全性和可靠性,可以采用 HTTPS 协议进行数据传输,并对下载链接进行时效性验证和权限控制。

三、总结

本文介绍了使用Spring Boot和EasyExcel实现大规模数据高效导出的方法,通过数据分批处理、并行处理和文件压缩等技术手段,提升了导出效率并优化了用户体验,特别适用于需要处理大量数据的企业系统。

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

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

相关文章

Conditional DETR论文笔记

原文链接 [2108.06152] Conditional DETR for Fast Training Convergencehttps://arxiv.org/abs/2108.06152 原文笔记 What 《Conditional DETR for Fast Training Convergence》 这个工作也是针对于DETR Query的工作 用于解决DETR训练收敛慢&#xff08;Object query需要…

在Excel中如何快速筛选非特定颜色

Excel中的自动筛选是个非常强大的工具&#xff0c;不仅可以筛选内容&#xff0c;而且可以筛选颜色&#xff0c;例如筛选A列红色单元格。但是有时希望筛选除了红色之外的单元格&#xff08;下图右侧所示&#xff09;&#xff0c;其他单元格的填充色不固定&#xff0c;有几种颜色…

C语言中的位操作

第一章 变量某位赋值与连续赋值 寄存器 | 值 //例如&#xff1a;a 1000 0011b a | (1<<2) //a 1000 0111 b 单独赋值 a | (3<<2*2) // 1011 0011b 连续赋值 第二章 变量某位清零与连续清零 寄存器 & ~&#xff08;&#xff09; 值 //例子&#xff1a;a …

微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖及性能分析

微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖及性能分析 目录 微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖及性能分析 1、iOS在scroll-view内部上下滑动吸顶的现象 正常的上下滑动吸顶覆盖&#xff1a; iOS及iPa…

详细分析Pytorch中的transpose基本知识(附Demo)| 对比 permute

目录 前言1. 基本知识2. Demo 前言 原先的permute推荐阅读&#xff1a;详细分析Pytorch中的permute基本知识&#xff08;附Demo&#xff09; 1. 基本知识 transpose 是 PyTorch 中用于交换张量维度的函数&#xff0c;特别是用于二维张量&#xff08;矩阵&#xff09;的转置操…

#PCIE#基础知识分解之 CC/SRNS/SRIS 时钟架构

参考资料为PCIe Base Spec和CEM Spec。 1.1 时钟架构分类 PCIe参考时钟的三种架构&#xff1a; Common Refclk (Shared Refclk) ArchitectureData Clocked Rx ArchitectureSeparate Refclk Architecture 下面&#xff0c;我们来简单地聊一聊前面说到的三种参考时钟架构&…

图书管理系统汇报

【1A536】图书管理系统汇报 项目介绍1.用户登录注册功能1. 1用户角色管理2.图书管理功能2.1 添加图书2.2 编辑图书2.3 删除图书 3.图书搜索和筛选3.1 图书搜索3.2 图书筛选 4.图书借阅、图书归还4.1 图书借阅4.2 图书归还 5.用户信息管理5.1上传头像5.2修改头像5.3 修改密码 项…

js 获取当前时间与前一个月时间

// 获取当前时间的毫秒数 var currentTimeMillis new Date().getTime();// 获取前一个月的Date对象 var dateLastMonth new Date(); dateLastMonth.setMonth(dateLastMonth.getMonth() - 1);// 获取前一个月的毫秒数 var timeMillisLastMonth dateLastMonth.getTime();conso…

Flutter InkWell组件去掉灰色遮罩

当InkerWell组件内部获取到焦点时&#xff0c;会展示一层灰色遮罩 将focusColor属性设置为透明即可 Flutter InkWell焦点效果源码分析 问题描述 当 InkWell 组件获得焦点时&#xff0c;会显示一层灰色遮罩效果。需要找出这个效果是由哪些组件控制的&#xff0c;以及具体的…

【SpringMVC】传递json,获取url参数,上传文件

【传递json数据】 【json概念】 一种轻量级数据交互格式&#xff0c;有自己的格式和语法&#xff0c;使用文本表示一个对象或数组的信息&#xff0c;其本质上是字符串&#xff0c;负责在不同的语言中数据传递与交换 json数据以字符串的形式体现 【json字符串与Java对象互转…

逆向 解密接口信息附Demo(二)

目录 前言1. 加密2. 解密 前言 原先写过另外一篇&#xff0c;推荐阅读&#xff1a;逆向 解密接口信息&#xff08;附Demo&#xff09; 下文以 https://login1.scrape.center/ 进行讲解&#xff0c; 1. 加密 登录过程中可以使用断点进行一步一步排查 或者在js文件中搜索enco…

【周末推荐】Windows无缝连接iPhone

关注“ONE生产力”&#xff0c;获取更多精彩推荐&#xff01; 又到了周末推荐时间了&#xff0c;今天我们介绍一个Windows内置的功能&#xff0c;能够帮助大家将自己的电脑和iPhone连接在一起。 很多用Windows的小伙伴羡慕macOS可以和iPhone无缝连接&#xff0c;轻松阅读和回…

015:地理信息系统开发平台ArcGIS Engine10.2与ArcGIS SDK for the Microsoft .NET Framework安装教程

摘要&#xff1a;本文详细介绍地理信息系统开发平台ArcGIS Engine10.2与ArcGIS SDK for the Microsoft .NET Framework的安装流程。 一、软件介绍 ArcGIS Engine 10.2是由Esri公司开发的一款强大的GIS&#xff08;地理信息系统&#xff09;开发平台。该软件基于ArcGIS 10.2 fo…

基于 Java 的 Spring Boot 和 Vue 的宠物领养系统设计与实现

需要代码 vx&#xff1a;Java980320 不收取任何费用 在这个宠物领养系统中&#xff0c;我们可以设定两个角色&#xff1a;管理员和普通用户。每个角色的功能和目标略有不同&#xff0c;以下分别介绍&#xff1a; 管理员 管理员的主要职责是确保平台的高效运行&#xff0c…

ES6 变量的解构赋值

数组的解构赋值 对象的解构赋值 字符串的解构赋值

关于我的数据结构与算法——初阶第二篇(排序)

&#xff08;叠甲&#xff1a;如有侵权请联系&#xff0c;内容都是自己学习的总结&#xff0c;一定不全面&#xff0c;仅当互相交流&#xff08;轻点骂&#xff09;我也只是站在巨人肩膀上的一个小卡拉米&#xff0c;已老实&#xff0c;求放过&#xff09;。 排序的概念及其运…

IDEA连接EXPRESS版本的SQL server数据库

我安装的版本是SQL2019-SSEI-Expr.exe 也就是EXPRESS版本的SQL Server安排非常简单和快速。 但是默认没有启动sa用户。 启动sa用户名密码登录 默认安装完以后没有启用。 使用Miscrosoft SQL Server Management Studio 使用Windows身份连接后。 在安全性》登录名中找到sa并修改…

Unity 实现的背包系统

Hello Inventory System Unity 实现的背包系统。 TEST 点击底部 TEST 按钮随机生成物品到 Chest &#xff1b;点击物品可以将其 “拿起” &#xff0c;按住键盘左侧的 Ctrl 键可以按半数拿起和放下&#xff1b;属于装备的物品可以点击右键装备上&#xff0c;显示在人物装备属性…

net mvc中使用vue自定义组件遇到的坑

自定义一个ButtonCounter.js组件 export default {data() {return {count: 0}},template: <van-button type"primary" click"count">You clicked me {{ count }} times.</van-button> }按照官网文档的意思&#xff0c;组件命名需要大写驼峰命…

docker基础篇(尚硅谷)

学习链接 docker1️⃣基础篇&#xff08;零基小白&#xff09; - 语雀文档 (即本篇) Docker与微服务实战&#xff08;基础篇&#xff09; Docker与微服务实战&#xff08;高级篇&#xff09;- 【上】 Docker与微服务实战&#xff08;高级篇&#xff09;- 【下】 文章目录 学习…