springboot应用大批量导出excel产生oom处理措施实践(适用于poieasyexcel)

一、背景:

在某些信息管理场景中,存在大批量导出需求,例如一次性导出10~100w行excel数据,如果不做特殊的处理,很容易导致Out Of Memory,特别是堆内存溢出。

oom复现

例如修改IDEA运行配置,VM参数初始堆为256m,最大堆为1G,垃圾回收器为G1

-Xms256m -Xmx1G -XX:+UseG1GC

在这里插入图片描述

Apply之后重启应用,重启之后打开Java VisualVM,连接刚启动的进程,关注监控tab页面的内存使用情况
在这里插入图片描述
使用poi导出,单批次为10w
在这里插入图片描述
等待一段时间,触发了oom异常:
在这里插入图片描述

在这里插入图片描述

此时其它业务操作也会受到影响,例如此处分页查询已经失效,只能重启临时解决。
在这里插入图片描述

二、分批处理实践

思路:每次查询并写入指定数量的excel行,文本设置为1w。

service代码:

package com.tgh.securitydemo.service;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tgh.securitydemo.entity.CommonWorkOrderLog;
import com.tgh.securitydemo.entity.excel.WorkOrderLogExportVO;
import com.tgh.securitydemo.entity.excel.WorkOrderLogSearchVO;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;/*** @author PineTree* @description: 导出服务* @date 2025/4/27 17:51*/
@Service
public class ExportService {@Autowiredprivate ICommonWorkOrderLogService commonWorkOrderLogService;private static final int batchSize = 10000; // 每批次大小public void exportExcel(WorkOrderLogSearchVO searchVO, HttpServletResponse response) throws IOException {// 设置响应头response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setHeader("Content-Disposition", "attachment; filename=work_order_logs.xlsx");if (searchVO.isEasy()) {if (searchVO.isBatch()) {handleEasyExcelBatch(searchVO, response);} else {handleEasyExcel(searchVO, response);}} else {if (searchVO.isBatch()) {handlePoiBatch(searchVO, response);} else {handlePoiExcel(searchVO, response);}}}private void handlePoiBatch(WorkOrderLogSearchVO searchVO, HttpServletResponse response) throws IOException {// 创建SXSSFWorkbook,设置行访问窗口大小为100SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保留3500行在内存中Sheet sheet = workbook.createSheet("日志数据-poi分批");boolean hasNext = true;try {int currentPage = 1;int rowNum = 0;Row row = sheet.createRow(rowNum++);generatePoiHead(row);while (hasNext) {// 分页查询数据IPage<CommonWorkOrderLog> pageSearch = new Page<>(currentPage, batchSize);IPage<CommonWorkOrderLog> pageData = commonWorkOrderLogService.getOrderPage(pageSearch, new CommonWorkOrderLog());List<CommonWorkOrderLog> records = pageData.getRecords();if (records.isEmpty()) {break; // 没有数据了,退出循环}for (CommonWorkOrderLog record : records) {Row poiRow = sheet.createRow(rowNum++);generatePoiDateRow(record, poiRow);}// 刷新已写入的行((SXSSFSheet)sheet).flushRows(batchSize);// 判断是否还有下一页hasNext = pageData.getCurrent() * batchSize < searchVO.getExportCount();currentPage++;}workbook.write(response.getOutputStream());} finally {workbook.dispose();workbook.close();}}private void handleEasyExcelBatch(WorkOrderLogSearchVO searchVO, HttpServletResponse response) throws IOException {ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), WorkOrderLogExportVO.class).build();try {WriteSheet writeSheet = EasyExcel.writerSheet("日志数据-easy分批").build();int currentPage = 1;boolean hasNext = true;while (hasNext) {// 分页查询数据IPage<CommonWorkOrderLog> pageSearch = new Page<>(currentPage, batchSize);IPage<CommonWorkOrderLog> pageData = commonWorkOrderLogService.getOrderPage(pageSearch, new CommonWorkOrderLog());List<CommonWorkOrderLog> records = pageData.getRecords();List<WorkOrderLogExportVO> exportData = records.stream().map(WorkOrderLogExportVO::fromEntity).collect(Collectors.toList());// 写入当前批次数据excelWriter.write(exportData, writeSheet);// 判断是否还有下一页hasNext = pageData.getCurrent() * batchSize < searchVO.getExportCount();currentPage++;}} finally {// 关闭ExcelWriterif (excelWriter != null) {excelWriter.finish();}}}private void handleEasyExcel(WorkOrderLogSearchVO searchVO, HttpServletResponse response) throws IOException {IPage<CommonWorkOrderLog> page = new Page<>(1, searchVO.getExportCount());IPage<CommonWorkOrderLog> orderPage = commonWorkOrderLogService.getOrderPage(page, new CommonWorkOrderLog());List<CommonWorkOrderLog> records = orderPage.getRecords();List<WorkOrderLogExportVO> exportData = records.stream().map(WorkOrderLogExportVO::fromEntity).collect(Collectors.toList());// 使用EasyExcel导出EasyExcel.write(response.getOutputStream(), WorkOrderLogExportVO.class).sheet("日志数据-easy单批").doWrite(exportData);}private void handlePoiExcel(WorkOrderLogSearchVO searchVO, HttpServletResponse response) throws IOException {IPage<CommonWorkOrderLog> page = new Page<>(1, searchVO.getExportCount());IPage<CommonWorkOrderLog> orderPage = commonWorkOrderLogService.getOrderPage(page, new CommonWorkOrderLog());List<CommonWorkOrderLog> records = orderPage.getRecords();// 创建工作簿Workbook workbook = new XSSFWorkbook();Sheet sheet = workbook.createSheet("日志数据-poi单批");// 创建表头Row headerRow = sheet.createRow(0);generatePoiHead(headerRow);// 填充数据int rowNum = 1;for (CommonWorkOrderLog record : records) {Row poiRow = sheet.createRow(rowNum++);generatePoiDateRow(record, poiRow);}// 写入响应流workbook.write(response.getOutputStream());workbook.close();}private static void generatePoiHead(Row headerRow) {headerRow.createCell(0).setCellValue("工单日志ID");headerRow.createCell(1).setCellValue("操作类型");headerRow.createCell(2).setCellValue("工单ID");headerRow.createCell(3).setCellValue("工单名称");headerRow.createCell(4).setCellValue("工单类型");headerRow.createCell(5).setCellValue("业务位置ID");headerRow.createCell(6).setCellValue("计划完成时间");headerRow.createCell(7).setCellValue("实际完成时间");headerRow.createCell(8).setCellValue("处理人账号");headerRow.createCell(9).setCellValue("处理人名称");headerRow.createCell(10).setCellValue("创建时间");headerRow.createCell(11).setCellValue("创建人名称");headerRow.createCell(12).setCellValue("最后更新人名称");headerRow.createCell(13).setCellValue("业务属性1");headerRow.createCell(14).setCellValue("业务属性2");}private static void generatePoiDateRow(CommonWorkOrderLog record, Row poiRow) {poiRow.createCell(0).setCellValue(record.getWorkOrderLogId());poiRow.createCell(1).setCellValue(record.getOperationType());poiRow.createCell(2).setCellValue(record.getWorkOrderId());poiRow.createCell(3).setCellValue(record.getWorkOrderName());poiRow.createCell(4).setCellValue(record.getWorkOrderType());poiRow.createCell(5).setCellValue(record.getBizLocationId());poiRow.createCell(6).setCellValue(record.getPlannedCompletionTime().toString());poiRow.createCell(7).setCellValue(ObjectUtils.isNotEmpty(record.getActualCompletionTime()) ? record.getActualCompletionTime().toString() : "");poiRow.createCell(8).setCellValue(record.getHandleUserAccount());poiRow.createCell(9).setCellValue(record.getHandleUserName());poiRow.createCell(10).setCellValue(record.getCreateTime().toString());poiRow.createCell(11).setCellValue(record.getCreateByName());poiRow.createCell(12).setCellValue(record.getLastUpdateName());poiRow.createCell(13).setCellValue(record.getBizAttribute1());poiRow.createCell(14).setCellValue(record.getBizAttribute2());}}

三、测试验证

poi分批导出10w

在这里插入图片描述

poi分批导出100w

在这里插入图片描述

easyexcel分批导出10w

在这里插入图片描述

easyexcel分批导出100w

在这里插入图片描述

四、结论

poieasyexcel大批量导出,均可通过分批思路来处理oom异常,easyexcel导出比poi慢,但是生成的文件小于poi,本文仅仅解决了大批量excel导出从不能导到能导。如果同时存在多用户,高频率,大批量导出同样会出问题,后续讨论…。

代码免费仓获取完整代码:
前端:https://gitee.com/pinetree-cpu/hello_vue3
后端:https://gitee.com/pinetree-cpu/parent-demon

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

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

相关文章

谷歌在即将举行的I/O大会之前,意外泄露了其全新设计语言“Material 3 Expressive”的细节

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

深入理解负载均衡:传输层与应用层的原理与实战

目录 前言1. 传输层&#xff08;Layer 4&#xff09;负载均衡1.1 工作层级与核心机制1.2 实现方式详解1.3 优缺点分析1.4 典型实现工具 2. 应用层&#xff08;Layer 7&#xff09;负载均衡2.1 工作层级与核心机制2.2 实现方式解析2.3 优缺点分析2.4 常用实现工具 3. Layer 4 与…

PyTorch 版本、torchvision 版本和 Python 版本的对应关系

PyTorch 版本、torchvision 版本和 Python 版本的对应关系 在深度学习领域&#xff0c;PyTorch 及其配套库 torchvision 的使用极为广泛。但不同版本的 PyTorch、torchvision 与 Python 之间存在严格的对应关系&#xff0c;若版本搭配不当&#xff0c;会导致代码运行出错…

【hadoop】Hbase java api 案例

代码实现&#xff1a; HBaseConnection.java package com.peizheng.bigdata;import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client…

荣耀A8互动娱乐组件部署实录(第3部分:控制端结构与房间通信协议)

作者&#xff1a;曾在 WebSocket 超时里泡了七天七夜的苦命人 一、控制端总体架构概述 荣耀A8控制端主要承担的是“运营支点”功能&#xff0c;也就是开发与运营之间的桥梁。它既不直接参与玩家行为&#xff0c;又控制着玩家的行为逻辑和游戏规则触发机制。控制端的主要职责包…

Vue3路由模式为history,使用nginx部署上线后刷新404的问题

一、问题 在使用nginx部署vue3的项目后&#xff0c;发现正常时可以访问的&#xff0c;但是一旦刷新&#xff0c;就是出现404的情况 二、解决方法 1.vite.config.js配置 在vite.config.js中加入以下配置 export default defineConfig(({ mode }) > {const isProduction …

企业级UI测试的“双保险”:TestComplete的智能对象识别与详细报告功能

企业级UI测试真是让人头疼&#xff01;界面元素变来变去&#xff0c;测试脚本动不动就报错&#xff0c;测试工作根本没法顺利推进。而且&#xff0c;测试结果的管理和共享也麻烦得很&#xff0c;团队协作效率大打折扣。别急&#xff01;TestComplete的智能对象识别和详细测试报…

SpringBoot的自动配置和起步依赖原理

关于Spring Boot的自动配置和起步依赖&#xff0c;我想结合最新的实现机制来展开说明。先说自动配置——这是Spring Boot最核心的"约定优于配置"思想的落地体现。举个例子&#xff0c;当我们创建一个新的Spring Boot项目时&#xff0c;只要在pom.xml里添加了spring-b…

《MATLAB实战训练营:从入门到工业级应用》高阶挑战篇-《5G通信速成:MATLAB毫米波信道建模仿真指南》

《MATLAB实战训练营&#xff1a;从入门到工业级应用》高阶挑战篇-5G通信速成&#xff1a;MATLAB毫米波信道建模仿真指南 &#x1f680;&#x1f4e1; 大家好&#xff01;今天我将带大家进入5G通信的奇妙世界&#xff0c;我们一起探索5G通信中最激动人心的部分之一——毫米波信…

一、Redis快速入门

Redis的常见命令和客户端使用 一、初识Redis Redis是一种键值型的NoSql数据库&#xff0c;这里有两个关键字&#xff1a; 键值型 NoSql 其中键值型&#xff0c;是指Redis中存储的数据都是以key、value对的形式存储&#xff0c;而value的形式多种多样&#xff0c;可以是字符…

gitcode 上传文件报错文件太大has exceeded the limited size (10 MiB) in commit

登陆gitcoe&#xff0c;在项目设置->提交设置 ,勾选提交文件限制&#xff0c;修改限制的大小。 修改完后&#xff0c;重新提交代码。

【运维】构建基于Python的自动化运维平台:用Flask和Celery打造高效管理工具

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着企业IT基础设施的复杂性不断增加,手动运维已无法满足高效管理的需求。本文详细介绍如何基于Python构建一个自动化运维平台,利用Flask…

基于大模型预测的产钳助产分娩全方位研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 二、产钳助产分娩概述 2.1 产钳助产定义与历史 2.2 适用情况与临床意义 三、大模型预测原理与数据基础 3.1 大模型技术原理 3.2 数据收集与处理 3.3 模型训练与验证 四、术前预测与准备 4.1 大模型术前风险预…

css使用aspect-ratio制作4:3和9:16和1:1等等比例布局

文章目录 1. 前言2. 用法2.1 基本语法2.2. 与max-width、max-height等属性结合使用2.3. 动态计算比例 3. 应用场景4. 兼容性和替代方案5. 总结 1. 前言 在网页制作过程中&#xff0c;有时候我们只知道宽度&#xff0c;或者只知道高度&#xff0c;这时候需要制作一个4:3和9:16这…

【国产化】在银河麒麟ARM环境下离线安装docker

1、前言 采用离线安装的方式。 关于离线安装的方式官网有介绍&#xff0c;但是说的很简单&#xff0c;网址&#xff1a;Binaries | Docker Docs 官网介绍的有几种主流linux系统的安装方式&#xff0c;但是没有kylin的&#xff0c;所以在此记录一下。 在安装过程中也遇到了些…

从一城一云到AI CITY,智慧城市进入新阶段

AI将如何改变城市面貌&#xff1f;AI能否为城市创造新的商业价值&#xff1f;AI的落地应用将对日常生活有什么样的影响&#xff1f; 几乎在每一场和城市发展相关的论坛上&#xff0c;都会出现以上几个问题。城市既是AI技术创新融合应用的综合性载体&#xff0c;普罗大众对AI产…

鸿蒙知识总结

判断题 1、 在http模块中&#xff0c;多个请求可以使用同一个httpRequest对象&#xff0c;httpRequest对象可以复用。&#xff08;错误&#xff09; 2、订阅dataReceiverProgress响应事件是用来接收HTTP流式响应数据。&#xff08;错误&#xff09; 3、ArkTS中变量声明时不需要…

[人机交互]理解用户

一.解释什么是认知&#xff0c;以及它对交互设计的重要性 1.1什么是认知 认知是指与knowing相关的能力&#xff0c;行为和过程&#xff08;考填空&#xff09; -如何感知物理刺激&#xff1f;如注意、知觉等 -如何认识自我、他人以及环境&#xff1f;如意识、记忆等 -如何…

微信小程序备案的一些记录

小程序如果没有备案是搜索不到小程序的。 小程序备案需要填写主体负责人的信息&#xff0c;需要主体负责人的手机号验证码&#xff0c; 需要填写管理员的信息&#xff0c;同样也需要验证手机号码&#xff0c; 填写完毕之后&#xff0c;提交进行初审&#xff0c;初审之后会打…

SpringCloud服务拆分:Nacos服务注册中心 + LoadBalancer服务负载均衡使用

SpringCloud中Nacos服务注册中心 LoadBalancer服务负载均衡使用 前言Nacos工作流程nacos安装docker安装window安装 运行nacos微服务集成nacos高级特性1.服务集群配置方法效果图模拟服务实例宕机 2.权重配置3.环境隔离 如何启动集群节点本地启动多个节点方法 LoadBalancer集成L…