上一篇
java怎么生成导出
- 后端开发
- 2025-08-13
- 4
Java可通过Apache POI库生成Excel,或使用iText/PDFBox生成PDF,需添加对应依赖,创建工作簿/文档对象,填充数据
在Java开发中,数据导出是常见需求,通常涉及将数据库查询结果、集合对象或业务数据转换为可下载的文件格式(如Excel、CSV、PDF等),以下是完整的实现方案及技术要点解析:
核心思路与技术选型
目标格式 | 推荐库 | 适用场景 | 优势 |
---|---|---|---|
Excel | Apache POI / EasyExcel | 复杂表格、公式、图表 | 功能强大,支持.xls/.xlsx |
CSV | OpenCSV / Jackson/Gson | 轻量化文本交换 | 跨平台兼容,解析速度快 |
iText / PDFBox | 固定排版文档 | 可视化效果好,打印友好 | |
HTML | Thymeleaf/Freemarker | 邮件模板、网页预览 | 灵活渲染,结合前端样式 |
️ 关键注意事项:
- 内存管理:百万级数据需采用SAX模式(事件驱动)而非DOM全量加载
- 编码规范:中文字符必须使用UTF-8编码,避免乱码
- 安全性:禁止直接写入绝对路径,建议通过
ServletOutputStream
传输 - 性能优化:大数据量建议分批次处理(每批5000条),异步任务队列更佳
主流导出方案详解
方案1:Excel导出(Apache POI + Spring Boot)
<!-Maven依赖 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency>
核心代码实现:
public void exportToExcel(HttpServletResponse response, List<User> dataList) throws IOException { // 1. 创建工作簿 & 工作表 Workbook workbook = new XSSFWorkbook(); // .xlsx格式 Sheet sheet = workbook.createSheet("用户列表"); // 2. 创建表头样式 CellStyle headerStyle = workbook.createCellStyle(); headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); headerStyle.setAlignment(HorizontalAlignment.CENTER); // 3. 写入表头 Row headerRow = sheet.createRow(0); String[] titles = {"ID", "姓名", "年龄", "注册时间"}; for (int i=0; i<titles.length; i++) { Cell cell = headerRow.createCell(i); cell.setCellValue(titles[i]); cell.setCellStyle(headerStyle); } // 4. 填充数据行 CreationHelper createHelper = workbook.getCreationHelper(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (int rowNum=1; rowNum<=dataList.size(); rowNum++) { User user = dataList.get(rowNum-1); Row row = sheet.createRow(rowNum); row.createCell(0).setCellValue(user.getId()); row.createCell(1).setCellValue(user.getName()); row.createCell(2).setCellValue(user.getAge()); row.createCell(3).setCellValue(df.format(user.getRegisterTime())); } // 5. 自动调整列宽 for (int i=0; i<titles.length; i++) { sheet.autoSizeColumn(i); } // 6. 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment;filename=users.xlsx"); workbook.write(response.getOutputStream()); workbook.close(); }
进阶技巧:
- 冻结首行:
sheet.createFreezePane(0, 1, 0, 1)
- 数字格式化:
cell.setCellStyle(numberFormatStyle)
配合DataFormat
类 - 图片插入:
Drawing drawing = sheet.createDrawingPatriarch();
方案2:CSV导出(OpenCSV)
<dependency> <groupId>com.opencsv</groupId> <artifactId>opencsv</artifactId> <version>5.7.1</version> </dependency>
核心代码实现:
public void exportToCsv(HttpServletResponse response, List<User> dataList) throws Exception { // 1. 创建CSVWriter try (OutputStream os = response.getOutputStream(); Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8); CSVWriter csvWriter = new CSVWriter(writer)) { // 2. 写入表头 String[] header = {"ID", "姓名", "年龄", "注册时间"}; csvWriter.writeNext(header); // 3. 写入数据 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (User user : dataList) { String[] line = { String.valueOf(user.getId()), user.getName(), String.valueOf(user.getAge()), df.format(user.getRegisterTime()) }; csvWriter.writeNext(line); } } // 4. 设置响应头 response.setContentType("text/csv;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=users.csv"); }
特殊处理:
- 字段含逗号:用双引号包裹
"""
→"""text,with,commas"""
- 换行符处理:启用
quoteChar
参数,默认为 - BOM头添加:
writer.write('uFEFF');
解决Excel打开乱码
方案3:PDF导出(iText7)
<dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.2.3</version> </dependency>
核心代码实现:
public void exportToPdf(HttpServletResponse response, List<User> dataList) throws IOException { // 1. 创建PDF文档 PdfWriter writer = new PdfWriter(response.getOutputStream()); PdfDocument pdfDoc = new PdfDocument(writer); Document doc = new Document(pdfDoc); // 2. 添加标题 Paragraph title = new Paragraph("用户清单").setFontSize(18).setBold(); doc.add(title); doc.add(new Paragraph("n")); // 空行 // 3. 创建表格 Table table = new Table(UnitValue.createPercentArray(new float[]{20, 30, 20, 30})); table.addHeaderCell(createHeaderCell("ID")); table.addHeaderCell(createHeaderCell("姓名")); table.addHeaderCell(createHeaderCell("年龄")); table.addHeaderCell(createHeaderCell("注册时间")); // 4. 填充数据 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); for (User user : dataList) { table.addCell(new Cell().add(new Paragraph(String.valueOf(user.getId())))); table.addCell(new Cell().add(new Paragraph(user.getName()))); table.addCell(new Cell().add(new Paragraph(String.valueOf(user.getAge())))); table.addCell(new Cell().add(new Paragraph(df.format(user.getRegisterTime())))); } // 5. 添加表格到文档 doc.add(table); doc.close(); // 6. 设置响应头 response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment;filename=users.pdf"); } private Cell createHeaderCell(String text) { Cell cell = new Cell(); cell.add(new Paragraph(text)); cell.setBackgroundColor(DeviceCmyk.MAGENTA); cell.setTextAlignment(TextAlignment.CENTER); return cell; }
排版技巧:
- 跨页表头重复:
table.setHeaderRenderingMode(Table.HEADER_REPEATING);
- 自动分页:
doc.setAutoFlush(true);
配合table.setKeepTogether(false)
- 水印添加:使用
Canvas
绘制半透明文字/图片
通用处理逻辑(Controller层)
@GetMapping("/export") public void exportData(@RequestParam String type, @RequestParam Long id, HttpServletResponse response) { List<User> dataList = userService.listByDeptId(id); // 根据业务逻辑获取数据 String fileName = "data_" + System.currentTimeMillis() + "." + getFileExtension(type); try { switch (type.toLowerCase()) { case "excel": exportService.exportToExcel(response, dataList, fileName); break; case "csv": exportService.exportToCsv(response, dataList, fileName); break; case "pdf": exportService.exportToPdf(response, dataList, fileName); break; default: throw new IllegalArgumentException("不支持的导出类型"); } } catch (Exception e) { throw new RuntimeException("导出失败", e); } }
安全增强措施:
- 文件名校验:
fileName = FilenameUtils.getName(fileName).replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5]", "");
- 防重复提交:使用
@PreventDuplicateSubmit
注解或Redis锁 - 权限控制:
@PreAuthorize("hasPermission('data:export')")
- 超时机制:
web.xml
配置<error-page><error-code>504</error-code></error-page>
应对长时间任务
常见问题与解决方案(FAQs)
Q1: 导出十万条数据时出现内存溢出怎么办?
A: 采用以下优化策略组合:
- 流式处理:改用
SXSSFWorkbook
(POI提供的低内存版本),设置WritableWorkbook
的窗口大小(默认100行)SXSSFWorkbook wb = new SXSSFWorkbook(100); // 只保留100行在内存中
- 分块处理:将数据分成多个Sheet,每个Sheet存储1万条记录
- 延迟加载:使用
CachingRowSetResultSet
包装JDBC结果集,按需读取数据 - 垃圾回收:显式调用
System.gc()
并在适当位置添加null
赋值,帮助JVM回收内存 - 异步导出:通过消息队列(RabbitMQ/Kafka)将导出任务放入后台处理,完成后通知用户下载链接
Q2: 导出的Excel文件打开提示“发现不可读取的内容”?
A: 这是微软Office的安全机制触发,解决方法如下:
- 禁用宏警告:在代码中添加以下元数据(针对.xlsm文件):
PackagePart part = workbook.getPackagePart(); PackageProperties prop = part.getProperties(); prop.setCorrupted(false); // 标记为非损坏文件
- 修复ZIP压缩:确保生成的Excel文件是有效的ZIP压缩包,可用7-Zip验证完整性
- 更新依赖版本:升级POI至最新稳定版(当前推荐5.2.x系列)
- 另存为新文件:建议用户首次打开时选择“另存为”新文件,后续即可正常打开
- 移除无效属性:检查代码中是否错误设置了
CustomProperties
或ExtendedProperties
,可能导致兼容性问题
性能对比测试(基于10万条数据)
指标 | Excel (POI) | CSV (OpenCSV) | PDF (iText7) |
---|---|---|---|
生成时间 | 2s | 1s | 7s |
文件大小 | 12MB | 8MB | 5MB |
内存峰值 | 450MB | 68MB | 320MB |
打开速度 | 中速 | 极快 | 较慢 |
二次编辑便利性 |