EasyPoi 导出百万数据实战
添加依赖#
<!-- easy poi --> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency> <!-- JSR 303 规范验证包 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.4.Final</version> </dependency>
工具类#

import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.TemplateExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.afterturn.easypoi.excel.export.ExcelExportService; import cn.afterturn.easypoi.excel.export.template.ExcelExportOfTemplateUtil; import cn.afterturn.easypoi.handler.inter.IExcelExportServer; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.util.Collection; import java.util.List; import java.util.Map; /** * Excel 大数据导出工具类 * * @Author:chenyanbin */ public class EasyPoiBigExportUtil { public static int USE_SXSSF_LIMIT = 100000; public static final String SHEET_NAME = "sheetName"; private EasyPoiBigExportUtil() { } /** * 大数据量导出 * * @param entity 表格标题属性 * @param pojoClass Excel对象Class * @param server 查询数据的接口 * @param queryParams 查询数据的参数 */ public static Workbook exportBigExcel(ExportParams entity, Class<?> pojoClass, IExcelExportServer server, Object queryParams) { EasyPoiBigBatchExportService batchServer = new EasyPoiBigBatchExportService(); batchServer.init(entity, pojoClass); return batchServer.exportBigExcel(server, queryParams); } /** * @param entity 表格标题属性 * @param pojoClass Excel对象Class * @param dataSet Excel对象数据List */ public static Workbook exportExcel(ExportParams entity, Class<?> pojoClass, Collection<?> dataSet) { Workbook workbook = getWorkbook(entity.getType(), dataSet.size()); new ExcelExportService().createSheet(workbook, entity, pojoClass, dataSet); return workbook; } private static Workbook getWorkbook(ExcelType type, int size) { if (ExcelType.HSSF.equals(type)) { return new HSSFWorkbook(); } else if (size < USE_SXSSF_LIMIT) { return new XSSFWorkbook(); } else { return new SXSSFWorkbook(); } } /** * 根据Map创建对应的Excel * * @param entity 表格标题属性 * @param entityList Map对象列表 * @param dataSet Excel对象数据List */ public static Workbook exportExcel(ExportParams entity, List<ExcelExportEntity> entityList, Collection<?> dataSet) { Workbook workbook = getWorkbook(entity.getType(), dataSet.size()); ; new ExcelExportService().createSheetForMap(workbook, entity, entityList, dataSet); return workbook; } /** * 根据Map创建对应的Excel(一个excel 创建多个sheet) * * @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data * Collection 数据 * @return */ public static Workbook exportExcel(List<Map<String, Object>> list, ExcelType type) { Workbook workbook = getWorkbook(type, 0); for (Map<String, Object> map : list) { ExcelExportService service = new ExcelExportService(); service.createSheet(workbook, (ExportParams) map.get("title"), (Class<?>) map.get("entity"), (Collection<?>) map.get("data")); } return workbook; } /** * 导出文件通过模板解析,不推荐这个了,推荐全部通过模板来执行处理 * * @param params 导出参数类 * @param pojoClass 对应实体 * @param dataSet 实体集合 * @param map 模板集合 * @return */ @Deprecated public static Workbook exportExcel(TemplateExportParams params, Class<?> pojoClass, Collection<?> dataSet, Map<String, Object> map) { return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, pojoClass, dataSet, map); } /** * 导出文件通过模板解析只有模板,没有集合 * * @param params 导出参数类 * @param map 模板集合 * @return */ public static Workbook exportExcel(TemplateExportParams params, Map<String, Object> map) { return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, null, null, map); } /** * 导出文件通过模板解析只有模板,没有集合 * 每个sheet对应一个map,导出到处,key是sheet的NUM * * @param params 导出参数类 * @param map 模板集合 * @return */ public static Workbook exportExcel(Map<Integer, Map<String, Object>> map, TemplateExportParams params) { return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, map); } /** * 导出文件通过模板解析只有模板,没有集合 * 每个sheet对应一个list,按照数量进行导出排序,key是sheet的NUM * * @param params 导出参数类 * @param map 模板集合 * @return */ public static Workbook exportExcelClone(Map<Integer, List<Map<String, Object>>> map, TemplateExportParams params) { return new ExcelExportOfTemplateUtil().createExcelCloneByTemplate(params, map); } }

import cn.afterturn.easypoi.excel.annotation.ExcelTarget; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.afterturn.easypoi.excel.export.ExcelExportService; import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler; import cn.afterturn.easypoi.exception.excel.ExcelExportException; import cn.afterturn.easypoi.exception.excel.enums.ExcelExportEnum; import cn.afterturn.easypoi.handler.inter.IExcelExportServer; import cn.afterturn.easypoi.util.PoiExcelGraphDataUtil; import cn.afterturn.easypoi.util.PoiPublicUtil; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static cn.afterturn.easypoi.excel.ExcelExportUtil.USE_SXSSF_LIMIT; /** * 提供批次插入服务 * * @Author:chenyanbin */ @Slf4j public class EasyPoiBigBatchExportService extends ExcelExportService { private static final Long KEEP_ALIVE_TIME = 60L; private static final int APS = Runtime.getRuntime().availableProcessors(); private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(APS * 2, APS * 4, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); private Workbook workbook; private Sheet sheet; private List<ExcelExportEntity> excelParams; private ExportParams entity; private int titleHeight; private Drawing patriarch; private short rowHeight; private int index; public void init(ExportParams entity, Class<?> pojoClass) { List<ExcelExportEntity> excelParams = createExcelExportEntityList(entity, pojoClass); init(entity, excelParams); } public void init(ExportParams entity, List<ExcelExportEntity> excelParams) { LOGGER.debug("ExcelBatchExportServer only support SXSSFWorkbook"); entity.setType(ExcelType.XSSF); workbook = new SXSSFWorkbook(); this.entity = entity; this.excelParams = excelParams; super.type = entity.getType(); createSheet(workbook, entity, excelParams); if (entity.getMaxNum() == 0) { entity.setMaxNum(USE_SXSSF_LIMIT); } insertDataToSheet(workbook, entity, excelParams, null, sheet); } public List<ExcelExportEntity> createExcelExportEntityList(ExportParams entity, Class<?> pojoClass) { try { List<ExcelExportEntity> excelParams = new ArrayList<ExcelExportEntity>(); if (entity.isAddIndex()) { excelParams.add(indexExcelEntity(entity)); } // 得到所有字段 Field[] fileds = PoiPublicUtil.getClassFields(pojoClass); ExcelTarget etarget = pojoClass.getAnnotation(ExcelTarget.class); String targetId = etarget == null ? null : etarget.value(); getAllExcelField(entity.getExclusions(), targetId, fileds, excelParams, pojoClass, null, null); sortAllParams(excelParams); return excelParams; } catch (Exception e) { throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e); } } public void createSheet(Workbook workbook, ExportParams entity, List<ExcelExportEntity> excelParams) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Excel export start ,List<ExcelExportEntity> is {}", excelParams); LOGGER.debug("Excel version is {}", entity.getType().equals(ExcelType.HSSF) ? "03" : "07"); } if (workbook == null || entity == null || excelParams == null) { throw new ExcelExportException(ExcelExportEnum.PARAMETER_ERROR); } try { try { sheet = workbook.createSheet(entity.getSheetName()); } catch (Exception e) { // 重复遍历,出现了重名现象,创建非指定的名称Sheet sheet = workbook.createSheet(); } } catch (Exception e) { throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e); } } public Workbook appendData(Collection<?> dataSet, int page) { if (sheet.getLastRowNum() + dataSet.size() > entity.getMaxNum()) { if (page > 1) { sheet = workbook.createSheet(); } index = 0; this.insertDataToSheet(this.workbook, entity, excelParams, (Collection) null, this.sheet); } Iterator<?> its = dataSet.iterator(); while (its.hasNext()) { Object t = its.next(); try { index += createCells(patriarch, index, t, excelParams, sheet, workbook, rowHeight, 0)[0]; } catch (Exception e) { LOGGER.error(e.getMessage(), e); throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e); } } return workbook; } @Override protected void insertDataToSheet(Workbook workbook, ExportParams entity, List<ExcelExportEntity> entityList, Collection<?> dataSet, Sheet sheet) { try { dataHandler = entity.getDataHandler(); if (dataHandler != null && dataHandler.getNeedHandlerFields() != null) { needHandlerList = Arrays.asList(dataHandler.getNeedHandlerFields()); } dictHandler = entity.getDictHandler(); i18nHandler = entity.getI18nHandler(); // 创建表格样式 setExcelExportStyler((IExcelExportStyler) entity.getStyle() .getConstructor(Workbook.class).newInstance(workbook)); patriarch = PoiExcelGraphDataUtil.getDrawingPatriarch(sheet); List<ExcelExportEntity> excelParams = new ArrayList<ExcelExportEntity>(); if (entity.isAddIndex()) { excelParams.add(indexExcelEntity(entity)); } excelParams.addAll(entityList); sortAllParams(excelParams); this.index = entity.isCreateHeadRows() ? createHeaderAndTitle(entity, sheet, workbook, excelParams) : 0; titleHeight = index; setCellWith(excelParams, sheet); setColumnHidden(excelParams, sheet); rowHeight = getRowHeight(excelParams); setCurrentIndex(1); } catch (Exception e) { LOGGER.error(e.getMessage(), e); throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e.getCause()); } } public Workbook closeExportBigExcel() { if (entity.getFreezeCol() != 0) { sheet.createFreezePane(entity.getFreezeCol(), titleHeight, entity.getFreezeCol(), titleHeight); } mergeCells(sheet, excelParams, titleHeight); // 创建合计信息 addStatisticsRow(getExcelExportStyler().getStyles(true, null), sheet); return workbook; } public Workbook exportBigExcel(IExcelExportServer server, Object queryParams) { AtomicInteger atomicInteger = new AtomicInteger(1); CountDownLatch latch = new CountDownLatch(1); THREAD_POOL_EXECUTOR.execute(() -> { while (true) { List<Object> list = server .selectListForExcelExport(queryParams, atomicInteger.get()); if (list.size() == 0) { break; } appendData(list, atomicInteger.get()); atomicInteger.incrementAndGet(); } latch.countDown(); }); try { latch.await(); } catch (InterruptedException e) { log.error("多线程导出异常:{}", e.getMessage()); } return closeExportBigExcel(); } }
导出代码#
import cn.afterturn.easypoi.excel.entity.ExportParams; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.util.List; import java.util.stream.Collectors; /** * 百万数据导出 * * @Author:chenyanbin */ @RestController @RequestMapping("/api/test/excel/v1") public class TestExcelController { @Autowired TestExcelMapper testExcelMapper; @GetMapping("export") public void export( HttpServletResponse response ) { Workbook workbook = null; ExportParams params = new ExportParams("大数据测试", "测试"); workbook = EasyPoiBigExportUtil.exportBigExcel(params, TestExcelDO.class, (queryParams, num) -> { System.err.println("第" + num + "页,查询参数:" + queryParams); //20万查询一次 IPage<TestExcelDO> page = new Page<>(num, 200000); LambdaQueryWrapper<TestExcelDO> wrapper = new LambdaQueryWrapper<>(); // wrapper.le(TestExcelDO::getId, 1500000); wrapper.le(TestExcelDO::getId, 500000); wrapper.orderByAsc(TestExcelDO::getId); List<TestExcelDO> listData = testExcelMapper.selectPage(page, wrapper).getRecords(); return listData.stream().map(obj -> (Object) obj).collect(Collectors.toList()); }, "1"); ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); response.setCharacterEncoding("UTF-8"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //mime类型 response.setHeader("Pragma", "No-cache");//设置不要缓存 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("大数据导出.xlsx", "UTF-8")); workbook.write(outputStream); outputStream.flush(); } catch (IOException e) { throw new RuntimeException(e); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

/** * <p> * Excel百万数据导出-勿动 * </p> * * @author chenyanbin * @since 2021-11-08 */ @Data @TableName("test_excel") @ApiModel(value = "TestExcelDO对象", description = "Excel百万数据导出-勿动") public class TestExcelDO implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; @Excel(name = "name1", width = 50) private String name1; @Excel(name = "name2", width = 50) private String name2; @Excel(name = "name3", width = 50) private String name3; @Excel(name = "name4", width = 50) private String name4; @Excel(name = "name5", width = 50) private String name5; @Excel(name = "name6", width = 50) private String name6; @Excel(name = "name7", width = 50) private String name7; @Excel(name = "name8", width = 50) private String name8; @Excel(name = "name9", width = 50) private String name9; @Excel(name = "name10", width = 50) private String name10; @Excel(name = "name11", width = 50) private String name11; @Excel(name = "name12", width = 50) private String name12; @Excel(name = "name13", width = 50) private String name13; @Excel(name = "name14", width = 50) private String name14; @Excel(name = "name15", width = 50) private String name15; }

import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** * <p> * Excel百万数据导出-勿动 Mapper 接口 * </p> * * @author chenyanbin * @since 2021-11-08 */ public interface TestExcelMapper extends BaseMapper<TestExcelDO> { int batchInsert(@Param("excelList") List<TestExcelDO> list); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?