目录
引言
**
在 Java 开发领域,数据的导出是一项极为常见且重要的任务。而将数据导出为 Excel 格式,由于 Excel 良好的兼容性和强大的数据展示与处理能力,成为了众多开发者的首选。Java POI(Poor Obfuscation Implementation)作为一个流行的 Java 库,为开发者提供了操作 Microsoft Office 格式文件(尤其是 Excel)的丰富 API,使得 Java 程序员能够在应用程序中方便地创建、读取和修改 Excel 文件。
在实际项目中,简单的数据导出场景往往难以满足复杂的业务需求。从基础的数据填充,到对单元格样式、字体、颜色的细致设置;从处理不同类型的数据,如文本、数字、日期,到应对大数据量导出时的性能优化;从单工作表导出,到多工作表、多 Sheet 页的协同处理,再到结合数据库查询结果、动态表头、合并单元格等多样化需求,Java POI 导出 Excel 面临着各种各样的复杂场景。
本文精心整理了 30 个 Java POI 导出 Excel 的复杂场景实战案例,这些案例均来源于实际项目经验与开发过程中的常见问题。通过对这些案例的深入剖析与实践,读者不仅能够全面掌握 Java POI 导出 Excel 的核心技术与高级技巧,还能学会如何根据不同的业务场景,灵活运用 POI 解决实际问题,提升开发效率与代码质量。无论你是 Java 开发的新手,还是经验丰富的程序员,相信本文的内容都能为你在 POI 导出 Excel 的应用中带来新的启发与帮助。
Java POI 基础回顾
(一)POI 简介
POI 是 Apache 软件基金会的开源项目,其功能十分强大,支持对多种 Microsoft Office 文件格式进行操作 ,如 Excel、Word、PowerPoint 等。在处理 Excel 文件时,POI 库提供了丰富的 API,允许开发者创建、读取、更新和删除 Excel 文件中的各种元素,包括工作表、行、列、单元格、数据格式、公式、图表等。它支持 Excel 97-2003 的.xls格式以及 Excel 2007 及以上版本的.xlsx格式,为不同场景下的 Excel 文件处理提供了全面的解决方案。
使用 POI 库具有诸多优势。首先,由于 POI 基于 Java 开发,Java 的跨平台特性使得 POI 可以在任何支持 Java 的操作系统上运行,无论是 Windows、Linux 还是 macOS,这极大地拓宽了其应用范围。其次,使用 POI 库进行 Excel 文件处理,无需在运行环境中安装 Microsoft Office 软件,降低了应用程序的部署成本和环境依赖。此外,POI 库提供了丰富且灵活的 API,开发者可以根据具体的业务需求,对 Excel 文件进行细致入微的控制,满足从简单的数据导出到复杂的报表生成等各种不同复杂度的需求。在 Java 开发领域,POI 库已经成为处理 Excel 文件的首选工具之一,广泛应用于企业级应用开发、数据分析、报表生成等众多场景中。
(二)核心类和接口
- HSSFWorkbook 和 XSSFWorkbook:HSSFWorkbook 用于处理 Excel 97-2003 版本的.xls文件,它是 Horrible Spreadsheet Format 的缩写,虽然名称中 “Horrible” 看似贬义,但它在处理早期 Excel 文件格式时发挥了重要作用。XSSFWorkbook 则用于处理 Excel 2007 及以上版本的.xlsx文件,其全称为 XML Spreadsheet Format,基于 XML 格式,相比.xls格式在数据存储和处理上更具优势,能够支持更大的数据量和更丰富的功能特性。创建 HSSFWorkbook 对象的示例代码如下:
HSSFWorkbook workbook = new HSSFWorkbook();
创建 XSSFWorkbook 对象的示例代码为:
XSSFWorkbook workbook = new XSSFWorkbook();
这两个类都实现了 Workbook 接口,Workbook 代表一个 Excel 工作簿,一个工作簿可以包含多个工作表(Sheet),是整个 Excel 文件操作的入口点。通过 Workbook 对象,我们可以创建、获取和管理工作表,设置工作簿的属性等。
- Sheet:Sheet 接口表示 Excel 工作簿中的一个工作表,一个 Workbook 可以包含多个 Sheet。常见的 Sheet 实现类有 HSSFSheet 和 XSSFSheet,分别对应.xls和.xlsx格式文件中的工作表。可以通过 Workbook 对象创建 Sheet,示例代码如下:
Sheet sheet = workbook.createSheet("Sheet1");
也可以通过索引获取已有的 Sheet:
Sheet sheet = workbook.getSheetAt(0);
Sheet 主要用于管理工作表中的行(Row)和列(虽然没有直接的 Column 对象,但可以通过 Cell 来间接管理列相关操作),以及设置工作表的一些属性,如工作表名称、默认列宽、打印设置等。
- Row:Row 接口代表工作表中的一行,一个 Sheet 由多个 Row 组成。常见的 Row 实现类有 HSSFRow 和 XSSFRow 。可以通过 Sheet 对象创建 Row,示例如下:
Row row = sheet.createRow(0); // 创建第一行
Row 主要用于创建和管理该行中的单元格(Cell),获取行号,设置行高以及对整行进行一些格式设置等操作。例如,可以设置某一行的行高:
row.setHeightInPoints(20); // 设置行高为20磅
- Cell:Cell 接口代表工作表中一行中的一个单元格,是 Excel 文件中数据存储和展示的最小单位。常见的 Cell 实现类有 HSSFCell 和 XSSFCell 。可以通过 Row 对象创建 Cell,示例代码如下:
Cell cell = row.createCell(0); // 在该行创建第一个单元格
Cell 可以设置和获取单元格的值,单元格的值类型丰富,包括数值型、文本型、布尔型、日期型等。同时,Cell 还支持设置单元格的样式,如字体、颜色、边框、背景色、对齐方式等,以及设置单元格的公式等操作。例如,设置单元格为文本类型并赋值:
cell.setCellType(CellType.STRING);
cell.setCellValue("Hello, POI!");
再如,为单元格设置公式:
cell.setCellFormula("SUM(A1:A10)");
这些核心类和接口相互协作,构成了 POI 库操作 Excel 文件的基础架构。通过灵活运用它们,开发者能够实现各种复杂的 Excel 文件处理功能。
实战案例 1 - 5:基础数据导出与样式设置
(一)简单数据导出
在实际项目中,从数据库读取数据并导出为 Excel 是一项常见任务。假设我们有一个用户信息表users,包含id、name、age和email字段 ,现在要将表中的数据导出到 Excel 文件。
首先,我们需要使用 JDBC 连接数据库并执行 SQL 查询获取数据,然后利用 Java POI 创建 Excel 工作簿并将数据写入其中。以下是完整的代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class SimpleDataExport {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/yourdb";
String username = "yourusername";
String password = "yourpassword";
String sql = "SELECT id, name, age, email FROM users";
try (Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("users.xlsx")) {
Sheet sheet = workbook.createSheet("用户信息");
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("邮箱");
// 写入数据
int rowNum = 1;
while (rs.next()) {
Row dataRow = sheet.createRow(rowNum++);
dataRow.createCell(0).setCellValue(rs.getInt("id"));
dataRow.createCell(1).setCellValue(rs.getString("name"));
dataRow.createCell(2).setCellValue(rs.getInt("age"));
dataRow.createCell(3).setCellValue(rs.getString("email"));
}
// 自动调整列宽
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
workbook.write(fos);
System.out.println("数据导出成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上述代码中,我们首先通过 JDBC 连接到 MySQL 数据库并执行 SQL 查询获取用户数据。然后,创建一个新的XSSFWorkbook对象代表 Excel 工作簿,并在其中创建一个名为 “用户信息” 的工作表。接着,创建表头行并设置表头单元格的值。在遍历结果集时,为每一行数据创建一个新的行对象,并将数据写入对应的单元格。最后,通过autoSizeColumn方法自动调整列宽以适应内容,并将工作簿写入文件系统。
(二)设置单元格样式
为了使导出的 Excel 文件更加美观和易读,我们经常需要设置单元格的样式,如字体、颜色、对齐方式、边框等。下面以设置字体为粗体、红色,背景色为黄色,水平居中对齐,以及添加边框为例,展示如何使用 Java POI 设置单元格样式。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CellStyleExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("styled_cells.xlsx")) {
Sheet sheet = workbook.createSheet("样式示例");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("设置样式的单元格");
// 创建单元格样式
CellStyle style = workbook.createCellStyle();
// 设置字体
Font font = workbook.createFont();
font.setBold(true);
font.setColor(IndexedColors.RED.getIndex());
style.setFont(font);
// 设置背景色
style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
// 设置边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 应用样式到单元格
cell.setCellStyle(style);
workbook.write(fos);
System.out.println("单元格样式设置成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,我们首先创建了一个XSSFWorkbook和一个工作表。然后,在工作表中创建了一行和一个单元格,并为单元格设置了初始值。接着,创建了一个CellStyle对象用于定义样式,通过Font对象设置字体的粗体和颜色属性,使用IndexedColors枚举来指定颜色索引。设置背景色时,通过setFillForegroundColor和setFillPattern方法实现。设置对齐方式使用setAlignment方法。对于边框设置,分别通过setBorderTop、setBorderBottom、setBorderLeft和setBorderRight方法设置四条边的边框样式为细边框。最后,将样式应用到单元格上,并将工作簿写入文件。
(三)合并单元格
合并单元格在展示一些特殊的数据结构或突出显示某些信息时非常有用,比如报表的表头合并。假设我们要创建一个简单的销售报表,表头部分需要合并一些单元格来展示报表的标题和日期范围。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class MergeCellsExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("merged_cells.xlsx")) {
Sheet sheet = workbook.createSheet("销售报表");
// 合并标题单元格
CellRangeAddress titleRange = new CellRangeAddress(0, 0, 0, 3);
sheet.addMergedRegion(titleRange);
Row titleRow = sheet.createRow(0);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue("2024年第一季度销售报表");
// 合并日期范围单元格
CellRangeAddress dateRange = new CellRangeAddress(1, 1, 0, 3);
sheet.addMergedRegion(dateRange);
Row dateRow = sheet.createRow(1);
Cell dateCell = dateRow.createCell(0);
dateCell.setCellValue("2024-01-01 至 2024-03-31");
// 创建表头列
Row headerRow = sheet.createRow(2);
headerRow.createCell(0).setCellValue("产品名称");
headerRow.createCell(1).setCellValue("销售数量");
headerRow.createCell(2).setCellValue("销售金额");
headerRow.createCell(3).setCellValue("利润率");
workbook.write(fos);
System.out.println("合并单元格设置成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,我们使用CellRangeAddress类来定义合并单元格的范围,其构造函数接收四个参数:起始行索引、结束行索引、起始列索引和结束列索引。通过sheet.addMergedRegion方法将定义好的合并范围添加到工作表中。在创建合并单元格后,我们在合并单元格的起始位置创建单元格并设置其值,从而实现标题和日期范围的合并展示。同时,还创建了普通的表头列用于后续数据的展示。
(四)设置列宽和行高
合理设置列宽和行高可以使 Excel 表格的数据展示更加清晰和美观。有时候,我们需要根据单元格内容自动调整列宽,以确保所有数据都能完整显示;而对于某些特殊行,可能需要设置固定的行高来突出显示或适应特定的格式要求。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class ColumnWidthAndRowHeightExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("column_row_size.xlsx")) {
Sheet sheet = workbook.createSheet("列宽和行高示例");
// 创建数据行
Row row1 = sheet.createRow(0);
row1.createCell(0).setCellValue("这是一个很长的文本内容,用于测试自动调整列宽");
row1.createCell(1).setCellValue(123456789);
// 自动调整第一列的列宽
sheet.autoSizeColumn(0);
// 设置第二列的固定列宽为30个字符宽度
sheet.setColumnWidth(1, 30 * 256);
// 设置第一行的固定行高为30磅
row1.setHeightInPoints(30);
workbook.write(fos);
System.out.println("列宽和行高设置成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,首先创建了一个包含两行数据的工作表。对于第一列,使用sheet.autoSizeColumn(0)方法自动调整列宽,使其能够适应单元格中的长文本内容。对于第二列,通过sheet.setColumnWidth(1, 30 * 256)方法设置固定列宽,这里的256是因为setColumnWidth方法的单位是 1/256 个字符宽度,所以30 * 256表示 30 个字符宽度。在设置行高方面,使用row1.setHeightInPoints(30)方法将第一行的行高设置为 30 磅,使该行在视觉上更加突出,也能更好地显示较长的文本内容。通过这种方式,我们可以灵活地控制 Excel 表格中列宽和行高,以满足不同的数据展示需求。
(五)数据格式化
在导出 Excel 数据时,经常需要对不同类型的数据进行格式化显示,如日期、数字等。例如,将日期格式化为 “yyyy-MM-dd” 的形式,将数字格式化为带有两位小数的百分比形式。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
public class DataFormattingExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("formatted_data.xlsx")) {
Sheet sheet = workbook.createSheet("数据格式化示例");
// 创建日期数据行
Row dateRow = sheet.createRow(0);
Cell dateCell = dateRow.createCell(0);
dateCell.setCellValue(new Date());
// 创建日期格式
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
dateCell.setCellStyle(dateStyle);
// 创建数字数据行
Row numberRow = sheet.createRow(1);
Cell numberCell = numberRow.createCell(0);
numberCell.setCellValue(0.25);
// 创建百分比格式
CellStyle percentStyle = workbook.createCellStyle();
percentStyle.setDataFormat(createHelper.createDataFormat().getFormat("0.00%"));
numberCell.setCellStyle(percentStyle);
workbook.write(fos);
System.out.println("数据格式化设置成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,首先创建了一个工作表,并在其中创建了两行数据,分别用于展示日期和数字的格式化效果。对于日期数据,通过workbook.getCreationHelper()获取CreationHelper对象,它提供了创建各种数据格式的方法。使用createHelper.createDataFormat().getFormat("yyyy-MM-dd")创建一个日期格式对象,并将其设置到CellStyle中,然后将该样式应用到日期单元格上,从而实现日期的格式化显示。对于数字数据,同样通过CreationHelper创建百分比格式对象,并设置到另一个CellStyle中,应用到数字单元格上,使数字以带有两位小数的百分比形式显示。通过这种方式,可以根据业务需求对不同类型的数据进行灵活的格式化处理,使导出的 Excel 数据更符合实际使用场景的要求。
实战案例 6 - 10:复杂数据处理与导出
(一)多 Sheet 导出
在实际业务中,我们经常需要将不同类型的数据分别存储在同一个 Excel 文件的多个 Sheet 中,以便于数据的分类管理和查看。比如,一个电商系统中,我们可能需要将订单数据、商品数据和用户数据分别导出到不同的 Sheet 中。
下面是一个使用 Java POI 实现多 Sheet 导出的代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MultipleSheetsExport {
public static void main(String[] args) {
// 模拟订单数据
List<List<String>> orderData = new ArrayList<>();
orderData.add(List.of("订单1", "商品A", "100", "张三"));
orderData.add(List.of("订单2", "商品B", "200", "李四"));
// 模拟商品数据
List<List<String>> productData = new ArrayList<>();
productData.add(List.of("商品A", "电子产品", "500"));
productData.add(List.of("商品B", "日用品", "300"));
// 模拟用户数据
List<List<String>> userData = new ArrayList<>();
userData.add(List.of("张三", "13800138000", "北京"));
userData.add(List.of("李四", "13900139000", "上海"));
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("multiple_sheets.xlsx")) {
// 创建订单Sheet
Sheet orderSheet = workbook.createSheet("订单数据");
writeDataToSheet(orderSheet, orderData);
// 创建商品Sheet
Sheet productSheet = workbook.createSheet("商品数据");
writeDataToSheet(productSheet, productData);
// 创建用户Sheet
Sheet userSheet = workbook.createSheet("用户数据");
writeDataToSheet(userSheet, userData);
workbook.write(fos);
System.out.println("多Sheet数据导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeDataToSheet(Sheet sheet, List<List<String>> data) {
int rowNum = 0;
for (List<String> rowData : data) {
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for (String cellValue : rowData) {
Cell cell = row.createCell(cellNum++);
cell.setCellValue(cellValue);
}
}
}
}
在上述代码中,我们首先创建了三个不同类型的数据列表,分别模拟订单数据、商品数据和用户数据。然后,创建了一个XSSFWorkbook对象代表 Excel 工作簿。接着,为每个数据类型创建一个对应的 Sheet,并通过writeDataToSheet方法将相应的数据写入到每个 Sheet 中。最后,将工作簿写入文件系统,完成多 Sheet 数据的导出。
(二)动态表头导出
在实际应用中,数据结构可能会根据业务需求的变化而变化,这就要求我们能够根据数据动态生成表头。例如,在一个数据分析项目中,用户可以选择不同的指标进行数据展示,此时表头就需要根据用户选择的指标动态生成。
以下是一个根据数据动态生成表头的代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DynamicHeaderExport {
public static void main(String[] args) {
// 模拟动态数据,假设数据是一个包含Map的列表,每个Map代表一行数据
List<Map<String, Object>> dynamicData = new ArrayList<>();
Map<String, Object> row1 = Map.of("姓名", "张三", "年龄", 25, "城市", "北京");
Map<String, Object> row2 = Map.of("姓名", "李四", "年龄", 30, "城市", "上海");
dynamicData.add(row1);
dynamicData.add(row2);
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("dynamic_header.xlsx")) {
Sheet sheet = workbook.createSheet("动态表头数据");
// 获取表头字段
List<String> headers = new ArrayList<>(dynamicData.get(0).keySet());
// 创建表头行
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers.get(i));
}
// 写入数据行
int rowNum = 1;
for (Map<String, Object> rowData : dynamicData) {
Row dataRow = sheet.createRow(rowNum++);
for (int i = 0; i < headers.size(); i++) {
Cell cell = dataRow.createCell(i);
cell.setCellValue(rowData.get(headers.get(i)).toString());
}
}
workbook.write(fos);
System.out.println("动态表头数据导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,我们首先定义了一个包含Map的列表来模拟动态数据,每个Map代表一行数据,其键值对表示列名和对应的数据值。然后,通过获取第一个Map的键集来确定表头字段,并创建表头行将这些字段写入。在写入数据行时,根据表头字段的顺序从每个Map中获取对应的数据并写入单元格,从而实现了根据动态数据生成表头并导出的功能。
(三)树形结构数据导出
树形结构数据在实际业务中很常见,如组织架构、文件目录等。将树形结构数据导出到 Excel 时,通常需要以缩进等方式来展示数据的层级关系,以便直观地呈现数据结构。
下面是一个将树形结构数据导出到 Excel 的代码实现思路及示例:
假设我们有一个简单的树形结构数据类TreeNode:
class TreeNode {
private String name;
private List<TreeNode> children;
public TreeNode(String name) {
this.name = name;
this.children = new ArrayList<>();
}
public void addChild(TreeNode child) {
children.add(child);
}
public String getName() {
return name;
}
public List<TreeNode> getChildren() {
return children;
}
}
导出树形结构数据的关键在于递归处理每个节点及其子节点,并根据层级设置缩进。以下是导出的代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class TreeStructureExport {
public static void main(String[] args) {
// 构建树形结构数据
TreeNode root = new TreeNode("根节点");
TreeNode child1 = new TreeNode("子节点1");
TreeNode child2 = new TreeNode("子节点2");
TreeNode grandChild1 = new TreeNode("孙节点1");
TreeNode grandChild2 = new TreeNode("孙节点2");
child1.addChild(grandChild1);
child2.addChild(grandChild2);
root.addChild(child1);
root.addChild(child2);
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("tree_structure.xlsx")) {
Sheet sheet = workbook.createSheet("树形结构数据");
exportTreeToExcel(sheet, root, 0, 0);
workbook.write(fos);
System.out.println("树形结构数据导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void exportTreeToExcel(Sheet sheet, TreeNode node, int rowNum, int level) {
Row row = sheet.createRow(rowNum);
Cell cell = row.createCell(0);
// 根据层级设置缩进
cell.setCellValue(" ".repeat(level * 4) + node.getName());
for (int i = 0; i < node.getChildren().size(); i++) {
exportTreeToExcel(sheet, node.getChildren().get(i), rowNum + 1 + i, level + 1);
}
}
}
在上述代码中,TreeNode类表示树形结构中的节点,包含节点名称和子节点列表。exportTreeToExcel方法通过递归的方式遍历树形结构,每递归一层,层级level加 1,通过" ".repeat(level * 4)实现缩进效果,将节点名称写入对应的单元格,并继续处理子节点,从而将树形结构数据以缩进的形式导出到 Excel 中。
(四)分组数据导出
在处理大量数据时,对数据进行分组展示可以提高数据的可读性和分析性。例如,在销售数据中,我们可能需要按地区、产品类别等进行分组展示。
下面是一个对数据进行分组并在 Excel 中展示分组结果的代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
public class GroupedDataExport {
public static void main(String[] args) {
// 模拟销售数据,包含地区、产品和销售额
List<SaleData> saleDataList = new ArrayList<>();
saleDataList.add(new SaleData("北京", "产品A", 1000));
saleDataList.add(new SaleData("北京", "产品B", 2000));
saleDataList.add(new SaleData("上海", "产品A", 1500));
saleDataList.add(new SaleData("上海", "产品B", 2500));
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("grouped_data.xlsx")) {
Sheet sheet = workbook.createSheet("分组销售数据");
// 按地区分组
Map<String, List<SaleData>> groupedByRegion = groupByRegion(saleDataList);
int rowNum = 0;
for (Map.Entry<String, List<SaleData>> entry : groupedByRegion.entrySet()) {
// 写入地区分组标题
Row groupHeaderRow = sheet.createRow(rowNum++);
Cell groupHeaderCell = groupHeaderRow.createCell(0);
groupHeaderCell.setCellValue("地区: " + entry.getKey());
// 写入分组数据表头
Row headerRow = sheet.createRow(rowNum++);
headerRow.createCell(0).setCellValue("产品");
headerRow.createCell(1).setCellValue("销售额");
// 写入分组数据
for (SaleData data : entry.getValue()) {
Row dataRow = sheet.createRow(rowNum++);
dataRow.createCell(0).setCellValue(data.getProduct());
dataRow.createCell(1).setCellValue(data.getAmount());
}
// 增加空行分隔不同分组
sheet.createRow(rowNum++);
}
workbook.write(fos);
System.out.println("分组数据导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
private static Map<String, List<SaleData>> groupByRegion(List<SaleData> dataList) {
Map<String, List<SaleData>> result = new HashMap<>();
for (SaleData data : dataList) {
result.putIfAbsent(data.getRegion(), new ArrayList<>());
result.get(data.getRegion()).add(data);
}
return result;
}
}
class SaleData {
private String region;
private String product;
private int amount;
public SaleData(String region, String product, int amount) {
this.region = region;
this.product = product;
this.amount = amount;
}
public String getRegion() {
return region;
}
public String getProduct() {
return product;
}
public int getAmount() {
return amount;
}
}
在这段代码中,SaleData类表示销售数据,包含地区、产品和销售额字段。groupByRegion方法通过遍历销售数据列表,使用HashMap将数据按地区进行分组。在导出时,首先写入地区分组标题,然后创建分组数据表头,接着遍历每个分组内的数据并写入相应的行,最后通过增加空行来分隔不同的分组,从而在 Excel 中清晰地展示分组数据。
(五)分页数据导出
当处理大数据量时,一次性将所有数据导出到 Excel 可能会导致内存溢出等问题。因此,需要采用分页导出的方式,将数据分批写入 Excel 文件。
以下是一个处理大数据量时分页导出到 Excel 的方法及代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class PagedDataExport {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/yourdb";
String username = "yourusername";
String password = "yourpassword";
String sql = "SELECT id, name, age, email FROM users";
int pageSize = 1000; // 每页数据量
try (Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
FileOutputStream fos = new FileOutputStream("paged_data.xlsx")) {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("分页数据");
int rowNum = 0;
int page = 1;
while (true) {
// 计算偏移量
int offset = (page - 1) * pageSize;
// 执行分页查询
ResultSet rs = stmt.executeQuery(sql + " LIMIT " + offset + ", " + pageSize);
boolean hasData = false;
while (rs.next()) {
if (!hasData) {
// 创建表头
if (rowNum == 0) {
Row headerRow = sheet.createRow(rowNum++);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("邮箱");
}
hasData = true;
}
// 写入数据
Row dataRow = sheet.createRow(rowNum++);
dataRow.createCell(0).setCellValue(rs.getInt("id"));
dataRow.createCell(1).setCellValue(rs.getString("name"));
dataRow.createCell(2).setCellValue(rs.getInt("age"));
dataRow.createCell(3).setCellValue(rs.getString("email"));
}
if (!hasData) {
break;
}
page++;
}
workbook.write(fos);
System.out.println("分页数据导出成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上述代码中,我们通过 JDBC 连接到数据库并执行分页查询。每次查询指定数量(pageSize)的数据,通过LIMIT子句实现分页。在导出过程中,首先判断是否为第一页数据,如果是则创建表头,然后逐行写入数据。当查询结果集中没有数据时,表示所有数据已导出完毕,退出循环,将工作簿写入文件,完成分页数据的导出,有效避免了大数据量导出时的内存问题。
实战案例 11 - 15:特殊数据类型与格式处理
(一)图片导出
在一些业务场景中,我们需要将图片插入到 Excel 单元格中,以增强数据的可视化效果或提供更多信息。例如,在产品目录导出中,可能需要在 Excel 中展示产品图片。
下面展示在 Excel 单元格中插入图片的代码实现,包括图片读取、转换和插入:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ImageExportExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("image_export.xlsx")) {
Sheet sheet = workbook.createSheet("图片示例");
// 读取图片文件
FileInputStream inputStream = new FileInputStream(new File("product_image.jpg"));
byte[] bytes = inputStream.readAllBytes();
inputStream.close();
// 将图片添加到工作簿
int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
CreationHelper helper = workbook.getCreationHelper();
Drawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
// 设置图片在单元格中的位置,这里设置在第一行第二列(索引从0开始)
anchor.setCol1(1);
anchor.setRow1(0);
// 插入图片
drawing.createPicture(anchor, pictureIdx);
// 设置图片所在列的宽度,使图片显示更合适
sheet.setColumnWidth(1, 4000);
workbook.write(fos);
System.out.println("图片导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,首先通过FileInputStream读取图片文件并将其转换为字节数组。然后,使用workbook.addPicture方法将图片添加到工作簿中,并返回图片的索引pictureIdx。接着,创建ClientAnchor对象来设置图片在单元格中的位置,setCol1和setRow1方法分别指定图片所在的列索引和行索引。通过drawing.createPicture方法将图片插入到指定位置。最后,设置图片所在列的宽度,使图片能够完整显示,并将工作簿写入文件。
(二)超链接添加
为单元格添加超链接可以方便用户在打开 Excel 文件时,直接点击链接跳转到相关的网页、文件或其他位置,提高数据的交互性和便捷性。比如在导出的员工信息表中,为员工的个人资料链接添加超链接,点击即可查看详细资料。
以下是介绍为单元格添加超链接的方法及代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class HyperlinkExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("hyperlink_example.xlsx")) {
Sheet sheet = workbook.createSheet("超链接示例");
// 创建第一行
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
// 创建CreationHelper以方便操作链接
CreationHelper creationHelper = workbook.getCreationHelper();
Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL);
// 设置超链接地址
hyperlink.setAddress("https://www.example.com");
// 给单元格赋值并设置超链接
cell.setCellValue("点击访问示例网站");
cell.setHyperlink(hyperlink);
// 设置超链接样式,使文本显示为蓝色下划线
CellStyle hyperlinkStyle = workbook.createCellStyle();
Font font = workbook.createFont();
font.setColor(IndexedColors.BLUE.getIndex());
font.setUnderline(Font.U_SINGLE);
hyperlinkStyle.setFont(font);
cell.setCellStyle(hyperlinkStyle);
workbook.write(fos);
System.out.println("超链接添加成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,首先创建了一个XSSFWorkbook和一个工作表。然后,获取CreationHelper对象并创建一个Hyperlink对象,设置其类型为URL,并指定链接地址。为单元格设置值后,将超链接对象设置到单元格上。为了使超链接的文本显示更明显,创建了一个CellStyle,设置字体颜色为蓝色并添加下划线,最后将该样式应用到单元格上,完成超链接的添加和样式设置。
(三)公式计算
在 Excel 中使用公式进行数据计算是其强大功能之一,通过 Java POI 我们可以在导出的 Excel 文件中设置公式,实现自动计算。例如,在导出销售报表时,计算销售额、利润等数据。
下面讲解在 Excel 中使用公式进行数据计算的方法及代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class FormulaExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("formula_example.xlsx")) {
Sheet sheet = workbook.createSheet("公式示例");
// 创建数据行
Row dataRow1 = sheet.createRow(0);
dataRow1.createCell(0).setCellValue(10);
dataRow1.createCell(1).setCellValue(20);
// 创建用于显示公式计算结果的单元格
Row resultRow = sheet.createRow(1);
Cell resultCell = resultRow.createCell(0);
// 设置公式,这里计算A1和B1单元格的和
resultCell.setCellFormula("A1+B1");
// 手动计算公式结果(如果需要立即获取计算结果)
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
CellValue cellValue = evaluator.evaluate(resultCell);
if (cellValue.getCellType() == CellType.NUMERIC) {
System.out.println("公式计算结果:" + cellValue.getNumberValue());
}
workbook.write(fos);
System.out.println("公式设置成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,首先创建了一个工作表,并在第一行的前两列分别设置了两个数值。然后,在第二行的第一列创建了一个单元格,并使用setCellFormula方法为其设置公式,该公式计算第一行前两列单元格的和。如果需要在导出时立即获取公式的计算结果,可以创建FormulaEvaluator对象并调用evaluate方法进行计算,根据计算结果的类型获取相应的值。最后,将工作簿写入文件,完成公式的设置和文件导出。
(四)批注添加
为单元格添加批注可以为数据提供额外的信息和说明,方便用户理解数据的含义或来源。比如在导出的财务报表中,为某些数据添加批注解释其计算方法或特殊情况。
下面说明为单元格添加批注以提供额外信息的方法及代码示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CommentExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("comment_example.xlsx")) {
Sheet sheet = workbook.createSheet("批注示例");
// 创建第一行
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("需要说明的数据");
// 创建绘图对象
Drawing drawing = sheet.createDrawingPatriarch();
CreationHelper factory = workbook.getCreationHelper();
// 创建批注的位置和大小
ClientAnchor anchor = factory.createClientAnchor();
anchor.setCol1(0);
anchor.setRow1(0);
anchor.setCol2(2);
anchor.setRow2(2);
// 创建批注对象
Comment comment = drawing.createCellComment(anchor);
RichTextString str = factory.createRichTextString("这是一个批注,用于说明数据的来源或特殊情况。");
comment.setString(str);
// 将批注添加到单元格
cell.setCellComment(comment);
workbook.write(fos);
System.out.println("批注添加成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,首先创建了一个工作表,并在第一行第一列创建了一个单元格并设置了值。接着,获取CreationHelper对象并创建Drawing对象用于绘制批注。通过createClientAnchor方法设置批注的位置和大小,其参数分别指定了批注起始和结束的列索引与行索引。创建Comment批注对象,并使用RichTextString设置批注的内容。最后,将批注添加到单元格上,并将工作簿写入文件,完成批注的添加。
(五)富文本处理
在单元格中显示富文本可以使数据展示更加丰富多样,例如设置不同部分文字的字体、颜色、加粗、倾斜等样式。比如在导出的报告中,对标题部分设置加粗、较大字体,对重要内容设置不同颜色。
以下展示在单元格中显示富文本(如加粗、倾斜、不同颜色文字)的代码实现:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class RichTextExample {
public static void main(String[] args) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("rich_text_example.xlsx")) {
Sheet sheet = workbook.createSheet("富文本示例");
// 创建第一行
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
// 创建富文本对象
XSSFRichTextString richText = new XSSFRichTextString();
// 定义不同字体样式
XSSFFont boldFont = workbook.createFont();
boldFont.setBold(true);
XSSFFont redFont = workbook.createFont();
redFont.setColor(IndexedColors.RED.getIndex());
// 组合文本片段并设置不同样式
richText.append("加粗文本:", boldFont);
richText.append("红色文本", redFont);
// 设置单元格值为富文本
cell.setCellValue(richText);
workbook.write(fos);
System.out.println("富文本设置成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,首先创建了一个工作表和一个单元格。然后,创建XSSFRichTextString对象用于存储富文本内容。接着,创建了两种不同的字体样式,一种是加粗字体boldFont,另一种是红色字体redFont。通过richText.append方法分片段添加文本,并为每个片段设置相应的字体样式。最后,将富文本对象设置为单元格的值,并将工作簿写入文件,实现了在单元格中显示具有不同样式的富文本效果。
实战案例 16 - 20:与数据库和其他系统集成
(一)从数据库直接导出
在许多企业级应用中,数据通常存储在数据库中,将数据库中的数据导出为 Excel 是常见需求。以 MySQL 数据库为例,以下是从数据库直接导出数据为 Excel 的完整代码示例,涵盖数据库连接和查询操作。
首先,确保项目中引入了 MySQL JDBC 驱动和 POI 库依赖。如果使用 Maven 构建项目,在pom.xml文件中添加如下依赖:
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
然后编写导出代码:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class DatabaseToExcelExport {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database";
String username = "your_username";
String password = "your_password";
String sql = "SELECT id, name, age, email FROM users";
try (Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("users_from_db.xlsx")) {
Sheet sheet = workbook.createSheet("用户数据");
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("邮箱");
// 写入数据
int rowNum = 1;
while (rs.next()) {
Row dataRow = sheet.createRow(rowNum++);
dataRow.createCell(0).setCellValue(rs.getInt("id"));
dataRow.createCell(1).setCellValue(rs.getString("name"));
dataRow.createCell(2).setCellValue(rs.getInt("age"));
dataRow.createCell(3).setCellValue(rs.getString("email"));
}
// 自动调整列宽
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
workbook.write(fos);
System.out.println("数据从数据库导出到Excel成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上述代码中,通过DriverManager.getConnection方法建立与 MySQL 数据库的连接,执行 SQL 查询语句获取结果集。然后创建XSSFWorkbook和Sheet对象,依次创建表头和数据行,并将数据写入对应的单元格。最后自动调整列宽并将工作簿写入文件系统。
(二)与 Spring Boot 集成
Spring Boot 作为流行的 Java 开发框架,简化了项目的配置和开发过程。在 Spring Boot 项目中使用 POI 导出 Excel,不仅能利用 Spring Boot 的依赖管理和自动配置功能,还能方便地与其他组件集成。
- 添加依赖:在pom.xml文件中添加 POI 和 Spring Boot 相关依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency> </dependencies>
- 创建导出服务:编写一个服务类,用于处理数据获取和 Excel 导出逻辑。假设我们有一个User实体类和对应的UserService。
import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; @Service public class ExcelExportService { public byte[] exportUsers(List<User> users) throws IOException { Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("用户列表"); // 创建表头 Row headerRow = sheet.createRow(0); headerRow.createCell(0).setCellValue("ID"); headerRow.createCell(1).setCellValue("姓名"); headerRow.createCell(2).setCellValue("年龄"); headerRow.createCell(3).setCellValue("邮箱"); // 写入数据 int rowNum = 1; for (User user : users) { Row dataRow = sheet.createRow(rowNum++); dataRow.createCell(0).setCellValue(user.getId()); dataRow.createCell(1).setCellValue(user.getName()); dataRow.createCell(2).setCellValue(user.getAge()); dataRow.createCell(3).setCellValue(user.getEmail()); } // 自动调整列宽 for (int i = 0; i < 4; i++) { sheet.autoSizeColumn(i); } // 将工作簿写入字节数组输出流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); workbook.write(bos); workbook.close(); return bos.toByteArray(); } }
- 创建控制器:创建一个 RESTful API 控制器,用于接收导出请求并返回 Excel 文件流。
import org.springframework.beans.factory.annotation.Autowired; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/api/export") public class ExcelExportController { @Autowired private ExcelExportService excelExportService; @Autowired private UserService userService; @GetMapping("/users") public ResponseEntity<byte[]> exportUsers() throws Exception { List<User> users = userService.getAllUsers(); byte[] excelBytes = excelExportService.exportUsers(users); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", "users.xlsx"); return new ResponseEntity<>(excelBytes, headers, org.springframework.http.HttpStatus.OK); } }
通过上述配置和代码实现,在 Spring Boot 项目中成功使用 POI 导出 Excel 文件,利用 Spring Boot 的依赖注入和 Web 开发特性,使代码结构更加清晰,易于维护和扩展。
(三)与 MyBatis 集成
MyBatis 是一款优秀的持久层框架,它提供了灵活的 SQL 映射和数据访问功能。将 MyBatis 查询数据与 POI 导出 Excel 相结合,可以高效地实现数据从数据库到 Excel 的导出。
- 添加依赖:在pom.xml文件中添加 MyBatis、MyBatis - Spring、MySQL JDBC 驱动和 POI 的依赖:
<dependencies> <dependency> <groupId>org.mybatis.spring</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency> </dependencies>
- 配置 MyBatis:在src/main/resources目录下创建mybatis-config.xml文件,配置 MyBatis 的基本设置和映射文件路径。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
- 编写 Mapper 接口和 XML 文件:定义一个UserMapper接口和对应的 XML 文件,用于查询用户数据。
import java.util.List; public interface UserMapper { List<User> getAllUsers(); }
UserMapper.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> <select id="getAllUsers" resultType="User"> SELECT id, name, age, email FROM users </select> </mapper>
- 创建导出服务:编写服务类,通过 MyBatis 获取数据并使用 POI 导出 Excel。
import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; @Service public class ExcelExportService { @Autowired private UserMapper userMapper; public byte[] exportUsers() throws IOException { List<User> users = userMapper.getAllUsers(); Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("用户列表"); // 创建表头 Row headerRow = sheet.createRow(0); headerRow.createCell(0).setCellValue("ID"); headerRow.createCell(1).setCellValue("姓名"); headerRow.createCell(2).setCellValue("年龄"); headerRow.createCell(3).setCellValue("邮箱"); // 写入数据 int rowNum = 1; for (User user : users) { Row dataRow = sheet.createRow(rowNum++); dataRow.createCell(0).setCellValue(user.getId()); dataRow.createCell(1).setCellValue(user.getName()); dataRow.createCell(2).setCellValue(user.getAge()); dataRow.createCell(3).setCellValue(user.getEmail()); } // 自动调整列宽 for (int i = 0; i < 4; i++) { sheet.autoSizeColumn(i); } // 将工作簿写入字节数组输出流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); workbook.write(bos); workbook.close(); return bos.toByteArray(); } }
通过这种方式,利用 MyBatis 的强大数据查询能力和 POI 的 Excel 操作功能,实现了高效的数据导出,同时优化了数据获取的过程,使代码更加简洁和可维护。
(四)从 RESTful API 获取数据导出
在微服务架构和前后端分离的开发模式中,经常需要从 RESTful API 获取数据并导出为 Excel。以下是实现从 RESTful API 获取数据并导出为 Excel 的步骤和代码示例。
假设我们有一个提供用户数据的 RESTful API,地址为http://localhost:8080/api/users,返回的数据格式为 JSON。
- 添加依赖:在项目中添加 POI 和 HTTP 客户端依赖,如okhttp。如果使用 Maven,在pom.xml中添加:
<dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.11.0</version> </dependency> </dependencies>
- 编写数据获取和导出代码:
import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; public class ApiToExcelExport { public static void main(String[] args) { String apiUrl = "http://localhost:8080/api/users"; OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(apiUrl) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); String jsonData = response.body().string(); List<User> users = new Gson().fromJson(jsonData, new TypeToken<List<User>>() {}.getType()); Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("用户数据"); // 创建表头 Row headerRow = sheet.createRow(0); headerRow.createCell(0).setCellValue("ID"); headerRow.createCell(1).setCellValue("姓名"); headerRow.createCell(2).setCellValue("年龄"); headerRow.createCell(3).setCellValue("邮箱"); // 写入数据 int rowNum = 1; for (User user : users) { Row dataRow = sheet.createRow(rowNum++); dataRow.createCell(0).setCellValue(user.getId()); dataRow.createCell(1).setCellValue(user.getName()); dataRow.createCell(2).setCellValue(user.getAge()); dataRow.createCell(3).setCellValue(user.getEmail()); } // 自动调整列宽 for (int i = 0; i < 4; i++) { sheet.autoSizeColumn(i); } try (FileOutputStream fos = new FileOutputStream("users_from_api.xlsx")) { workbook.write(fos); System.out.println("数据从API导出到Excel成功!"); } } catch (IOException e) { e.printStackTrace(); } } } class User { private int id; private String name; private int age; private String email; // 生成Getter和Setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
在上述代码中,使用okhttp库发送 HTTP GET 请求到指定的 API 地址获取数据,通过Gson库将返回的 JSON 数据解析为User对象列表。然后利用 POI 创建 Excel 工作簿,将数据写入 Excel 文件,实现了从 RESTful API 获取数据并导出为 Excel 的功能,方便了不同系统间的数据交互和共享。
(五)与其他文件格式互转
在实际的数据处理过程中,有时需要将 Excel 数据转换为其他文件格式,如 CSV(逗号分隔值)格式,以满足不同系统或工具的需求。以下展示将 Excel 数据转换为 CSV 格式的代码实现。
假设我们有一个已存在的 Excel 文件input.xlsx,要将其内容转换为 CSV 文件output.csv。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
public class ExcelToCsvConverter {
public static void main(String[] args) {
String excelFilePath = "input.xlsx";
String csvFilePath = "output.csv";
try (Workbook workbook = new XSSFWorkbook(new FileInputStream(excelFilePath));
PrintWriter pw = new PrintWriter(new FileWriter(csvFilePath))) {
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
StringBuilder sb = new StringBuilder();
for (Cell cell : row) {
String cellValue = getCellValue(cell);
sb.append(cell
## 实战案例21 - 25:性能优化与异常处理
### (一)大数据量导出优化
当面临大数据量导出时,传统的`XSSFWorkbook`和`HSSFWorkbook`可能会导致内存占用过高,甚至内存溢出。为了解决这个问题,Apache POI提供了`SXSSFWorkbook`类,它基于`XSSFWorkbook`,采用了一种“滑动窗口”的机制,将数据分批写入磁盘,从而大大减少了内存的使用。
以下是使用`SXSSFWorkbook`进行大数据量导出的代码示例:
```java
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class BigDataExportOptimization {
public static void main(String[] args) {
int rowCount = 100000; // 假设要导出10万行数据
try (Workbook workbook = new SXSSFWorkbook();
FileOutputStream fos = new FileOutputStream("big_data_export.xlsx")) {
Sheet sheet = workbook.createSheet("大数据量导出");
// 设置窗口大小,即内存中保留的行数,超过这个数量的行将被写入磁盘
((SXSSFWorkbook) workbook).setCompressTempFiles(true);
((SXSSFWorkbook) workbook).setRowAccessWindowSize(100);
// 写入数据
for (int i = 0; i < rowCount; i++) {
Row row = sheet.createRow(i);
for (int j = 0; j < 5; j++) {
Cell cell = row.createCell(j);
cell.setCellValue("数据行 " + i + ",列 " + j);
}
}
workbook.write(fos);
System.out.println("大数据量导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,创建SXSSFWorkbook对象时,通过setRowAccessWindowSize方法设置了窗口大小为 100,这意味着在内存中最多保留 100 行数据,当超过这个数量时,最早的行将被写入磁盘,从而有效控制了内存的使用。同时,setCompressTempFiles(true)方法用于设置是否压缩临时文件,以节省磁盘空间。通过这种方式,即使面对大量数据,也能高效地完成导出任务,避免因内存不足导致的程序崩溃。
(二)导出进度监控
在进行数据导出时,尤其是大数据量导出,用户往往希望了解导出的进度,以便预估等待时间和确认操作是否正常进行。实现导出进度监控可以显著提升用户体验,让用户在操作过程中更加安心。
实现导出进度监控的一种常见思路是利用多线程和回调机制。通过在一个单独的线程中执行导出任务,主线程可以实时获取导出的进度并更新进度条或其他可视化组件。
以下是一个简单的实现导出进度监控的代码示例,使用 Swing 组件展示进度条:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileOutputStream;
import java.io.IOException;
public class ExportProgressMonitor {
private static JProgressBar progressBar;
private static JButton exportButton;
public static void main(String[] args) {
JFrame frame = new JFrame("导出进度示例");
progressBar = new JProgressBar(0, 100);
exportButton = new JButton("开始导出");
exportButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exportButton.setEnabled(false);
new Thread(() -> {
exportData();
}).start();
}
});
frame.setLayout(new BorderLayout());
frame.add(progressBar, BorderLayout.NORTH);
frame.add(exportButton, BorderLayout.CENTER);
frame.setSize(300, 150);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static void exportData() {
int totalRows = 1000; // 假设要导出1000行数据
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("progress_monitor_export.xlsx")) {
Sheet sheet = workbook.createSheet("数据导出");
for (int i = 0; i < totalRows; i++) {
Row row = sheet.createRow(i);
for (int j = 0; j < 3; j++) {
Cell cell = row.createCell(j);
cell.setCellValue("数据行 " + i + ",列 " + j);
}
// 更新进度条
int progress = (int) ((i + 1) * 100.0 / totalRows);
progressBar.setValue(progress);
}
workbook.write(fos);
SwingUtilities.invokeLater(() -> {
progressBar.setValue(100);
exportButton.setEnabled(true);
JOptionPane.showMessageDialog(null, "导出完成");
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这段代码中,创建了一个包含进度条和导出按钮的 Swing 窗口。当用户点击导出按钮时,会启动一个新线程执行导出任务。在导出过程中,每处理一行数据,就计算当前的导出进度并更新进度条的值。导出完成后,通过SwingUtilities.invokeLater方法在事件调度线程中更新进度条为 100%,重新启用导出按钮,并弹出导出完成的提示框,从而实现了直观的导出进度监控功能,提升了用户体验。
(三)内存管理
在使用 Java POI 进行 Excel 导出时,合理的内存管理至关重要,尤其是在处理大量数据或频繁进行导出操作时。不当的内存管理可能导致内存泄漏,使应用程序的性能逐渐下降,甚至引发内存溢出错误,导致程序崩溃。
以下是在导出过程中合理管理内存的方法及注意事项:
- 及时关闭资源:在导出完成后,务必及时关闭Workbook、FileOutputStream等相关资源。例如:
try (Workbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream("example.xlsx")) { // 导出操作 } catch (IOException e) { e.printStackTrace(); }
- 避免不必要的对象创建:在循环中尽量避免创建不必要的对象,尤其是那些占用内存较大或创建开销较高的对象。例如,在设置单元格样式时,可以预先创建好CellStyle对象,而不是在每次循环中都创建新的对象:
CellStyle style = workbook.createCellStyle(); // 设置样式属性 for (int i = 0; i < rowCount; i++) { Row row = sheet.createRow(i); Cell cell = row.createCell(0); cell.setCellStyle(style); // 其他操作 }
- 使用合适的 POI 类:如前文所述,对于大数据量导出,使用SXSSFWorkbook代替XSSFWorkbook可以有效减少内存占用。SXSSFWorkbook通过将数据分批写入磁盘,避免了大量数据在内存中堆积。
- 注意对象引用:确保在不再需要某个对象时,及时释放对它的引用,以便垃圾回收器能够回收该对象所占用的内存。例如,当不再需要某个Sheet或Row对象时,将其赋值为null:
Sheet sheet = workbook.createSheet("临时Sheet"); // 使用sheet sheet = null; // 释放对sheet的引用,便于垃圾回收
- 内存监控与调优:在开发和测试阶段,可以使用 Java 的内存分析工具(如 VisualVM、YourKit 等)对应用程序的内存使用情况进行监控,及时发现内存泄漏和内存使用不合理的地方,并进行相应的优化。通过合理的内存管理,能够确保 POI 导出 Excel 的过程高效、稳定,避免因内存问题影响应用程序的正常运行。
(四)异常处理
在 POI 导出过程中,可能会出现各种异常,如文件读写异常、数据格式转换异常等。有效地处理这些异常对于确保程序的稳定性和健壮性至关重要,能够避免因异常导致的程序崩溃,提高用户体验。
- 常见异常类型及原因
- IOException:这是最常见的异常之一,通常在文件读写操作时抛出,例如文件不存在、无法创建文件、写入文件失败等。例如,在使用FileOutputStream将Workbook写入文件时,如果目标文件所在目录不存在或没有写入权限,就会抛出IOException。
- IllegalArgumentException:当传递给方法的参数不合法时会抛出此异常。在 POI 中,例如设置单元格样式时,如果传入的颜色索引或其他参数超出了合法范围,就可能引发IllegalArgumentException。
- NullPointerException:当尝试访问一个空对象的方法或属性时会抛出该异常。在 POI 导出中,如果没有正确初始化Workbook、Sheet、Row或Cell等对象,就可能导致空指针异常。例如,在获取Sheet对象后没有进行非空判断就直接调用其方法,当Sheet为null时就会抛出NullPointerException。
2. 异常处理方法
-
- 使用 try - catch 块捕获异常:在可能抛出异常的代码周围使用try - catch块进行捕获,并根据不同的异常类型进行相应的处理。例如:
try { Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("示例Sheet"); // 其他导出操作 FileOutputStream fos = new FileOutputStream("example.xlsx"); workbook.write(fos); fos.close(); } catch (IOException e) { e.printStackTrace(); System.err.println("文件读写错误: " + e.getMessage()); // 可以在这里添加更具体的错误处理逻辑,如提示用户重新操作或记录日志 } catch (IllegalArgumentException e) { e.printStackTrace(); System.err.println("参数错误: " + e.getMessage()); } catch (NullPointerException e) { e.printStackTrace(); System.err.println("空指针错误: " + e.getMessage()); }
- 使用 try - catch 块捕获异常:在可能抛出异常的代码周围使用try - catch块进行捕获,并根据不同的异常类型进行相应的处理。例如:
- 记录异常信息:在捕获异常后,使用日志记录工具(如 Log4j、SLF4J 等)记录异常信息,包括异常类型、异常信息和堆栈跟踪信息,以便后续排查问题。例如,使用 SLF4J 和 Logback 记录异常:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ExcelExporter { private static final Logger logger = LoggerFactory.getLogger(ExcelExporter.class); public void export() { try { // 导出操作 } catch (Exception e) { logger.error("导出Excel时发生错误", e); } } }
通过合理的异常处理机制,能够使程序在面对各种异常情况时保持稳定,及时向用户反馈错误信息,并为开发人员提供详细的错误排查依据,从而提高整个应用程序的可靠性。
(五)导出失败恢复
在 POI 导出过程中,由于各种原因(如网络中断、磁盘空间不足、数据库连接异常等),可能会导致导出失败。当导出失败时,提供有效的数据恢复机制或清晰的用户提示非常重要,以确保数据的完整性和用户的操作体验。
- 数据恢复方法
-
- 记录导出进度:在导出过程中,记录已成功导出的数据行数或其他标识信息。例如,可以在每次成功写入一行数据后,将当前行号记录到一个临时文件或数据库中。当导出失败时,可以根据记录的进度,从上次中断的位置重新开始导出。以下是一个简单的记录导出进度的示例:
import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.*; public class ExportRecovery { private static final String PROGRESS_FILE = "export_progress.txt"; public static void main(String[] args) { int totalRows = 1000; // 假设要导出1000行数据 int startRow = getExportProgress(); try (Workbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream("recoverable_export.xlsx")) { Sheet sheet = workbook.createSheet("可恢复导出"); for (int i = startRow; i < totalRows; i++) { Row row = sheet.createRow(i); for (int j = 0; j < 3; j++) { Cell cell = row.createCell(j); cell.setCellValue("数据行 " + i + ",列 " + j); } // 记录导出进度 saveExportProgress(i + 1); } // 导出成功,删除进度文件 new File(PROGRESS_FILE).delete(); System.out.println("数据导出成功!"); } catch (IOException e) { e.printStackTrace(); System.err.println("导出失败,可能需要重新导出。"); } } private static int getExportProgress() { File progressFile = new File(PROGRESS_FILE); if (!progressFile.exists()) { return 0; } try (BufferedReader reader = new BufferedReader(new FileReader(progressFile))) { return Integer.parseInt(reader.readLine()); } catch (IOException e) { e.printStackTrace(); return 0; } } private static void saveExportProgress(int row) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(PROGRESS_FILE))) { writer.write(String.valueOf(row)); } catch (IOException e) { e.printStackTrace(); } } }
- 记录导出进度:在导出过程中,记录已成功导出的数据行数或其他标识信息。例如,可以在每次成功写入一行数据后,将当前行号记录到一个临时文件或数据库中。当导出失败时,可以根据记录的进度,从上次中断的位置重新开始导出。以下是一个简单的记录导出进度的示例:
- 使用事务(如果涉及数据库操作):如果导出数据来源于数据库,并且在导出过程中对数据库进行了读取操作,可以使用数据库事务来确保数据的一致性。如果导出过程中发生错误,可以回滚事务,避免数据的部分导出导致的数据不一致问题。例如,在使用 JDBC 进行数据库查询并导出时,可以如下使用事务:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DatabaseExportWithTransaction { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/yourdb"; String username = "yourusername"; String password = "yourpassword"; String sql = "SELECT id, name, age FROM users"; try (Connection conn = DriverManager.getConnection(url, username, password)) { conn.setAutoCommit(false); PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(); // 进行导出操作 // 如果导出过程中发生错误,抛出异常 throw new SQLException("模拟导出过程中的错误"); conn.commit(); System.out.println("数据导出成功!"); } catch (SQLException e) { e.printStackTrace(); // 回滚事务 try (Connection conn = DriverManager.getConnection(url, username, password)) { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } System.err.println("导出失败,事务已回滚。"); } } }
2. 提示用户:无论是否能够恢复数据,都应该向用户提供清晰的错误提示,告知用户导出失败的原因以及可能的解决方法。可以通过弹出对话框、在界面上显示错误信息或发送通知等方式向用户传达这些信息。例如,在 Swing 应用程序中,可以使用JOptionPane弹出错误提示框:
import javax.swing.*; public class ExportErrorPrompt { public static void main(String[] args) { try { // 导出操作 } catch (Exception e) { JOptionPane.showMessageDialog(null, "导出失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } }
通过上述数据恢复方法和用户提示机制,能够在导出失败时尽量减少数据损失,并为用户提供良好的交互体验,让用户清楚了解导出失败的情况及后续操作建议。
实战案例 26 - 30:高级应用与定制化
(一)模板导出
在实际业务中,很多时候需要按照固定的格式导出 Excel 文件,如财务报表、销售统计报表等,这些报表往往具有相同的表头、表尾和固定的格式设置。使用 Excel 模板填充数据导出的方式,可以大大提高导出效率和规范性,避免重复编写设置格式的代码。
以下展示使用 Excel 模板填充数据导出的代码实现,假设我们已经有一个 Excel 模板文件template.xlsx,模板中包含固定的表头和一些占位符,用于填充动态数据:
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.TextRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.MiniTableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TemplateExportExample {
public static void main(String[] args) {
try (InputStream is = new FileInputStream("template.xlsx");
FileOutputStream fos = new FileOutputStream("filled_template.xlsx")) {
// 准备填充数据
Map<String, Object> data = new HashMap<>();
data.put("companyName", "示例公司");
data.put("reportDate", "2024-10-01");
data.put("operator", "张三");
// 动态数据列表
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> item1 = new HashMap<>();
item1.put("productName", "产品A");
item1.put("quantity", 100);
item1.put("price", 10.5);
item1.put("total", 100 * 10.5);
dataList.add(item1);
Map<String, Object> item2 = new HashMap<>();
item2.put("productName", "产品B");
item2.put("quantity", 200);
item2.put("price", 15.3);
item2.put("total", 200 * 15.3);
dataList.add(item2);
data.put("dataList", dataList);
// 使用POI-TL渲染模板
Configure config = Configure.newBuilder()
.bind("dataList", new MiniTableRenderPolicy() {
@Override
public void render(XSSFTable table, Object data, Configure configure) {
List<RowRenderData> rows = (List<RowRenderData>) data;
XSSFSheet sheet = table.getSheet();
int startRow = table.getStartRow();
for (int i = 0; i < rows.size(); i++) {
RowRenderData rowData = rows.get(i);
XSSFRow row = sheet.getRow(startRow + i + 1);
if (row == null) {
row = sheet.createRow(startRow + i + 1);
}
for (int j = 0; j < rowData.size(); j++) {
TextRenderData cellData = rowData.getCellData(j);
XSSFCell cell = row.getCell(j);
if (cell == null) {
cell = row.createCell(j);
}
cell.setCellValue(cellData.getText());
}
}
}
})
.build();
XWPFTemplate template = XWPFTemplate.compile(is, config).render(data);
template.write(fos);
template.close();
System.out.println("模板导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,首先通过FileInputStream读取 Excel 模板文件。然后准备填充数据,包括固定的表头数据和动态的数据列表。使用 POI-TL 库进行模板渲染,通过Configure配置自定义的渲染策略,这里使用MiniTableRenderPolicy来处理动态数据列表的渲染,根据数据列表中的内容填充到模板中的相应位置。最后将渲染后的模板写入到新的 Excel 文件中,完成模板导出操作,这种方式使得导出的 Excel 文件格式统一,易于维护和修改。
(二)自定义导出策略
在不同的业务场景下,根据用户角色、业务需求等因素,可能需要自定义导出策略,例如不同角色的用户导出的数据范围、字段不同,或者根据某些条件筛选数据后再导出。
以下讲解根据业务需求自定义导出策略(如不同角色导出不同数据)的方法及代码示例:
假设我们有一个用户管理系统,管理员角色可以导出所有用户信息,普通用户角色只能导出自己的信息。定义用户类User和角色枚举Role:
class User {
private int id;
private String name;
private int age;
private String email;
private Role role;
// 生成Getter和Setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
enum Role {
ADMIN, NORMAL_USER
}
导出策略实现代码如下:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class CustomExportStrategy {
public static void export(List<User> users, Role currentRole, String filePath) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream(filePath)) {
Sheet sheet = workbook.createSheet("用户数据");
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("邮箱");
// 根据角色筛选数据并导出
int rowNum = 1;
for (User user : users) {
if (currentRole == Role.ADMIN || (currentRole == Role.NORMAL_USER && user.getRole() == Role.NORMAL_USER && user.getId() == 1)) {
Row dataRow = sheet.createRow(rowNum++);
dataRow.createCell(0).setCellValue(user.getId());
dataRow.createCell(1).setCellValue(user.getName());
dataRow.createCell(2).setCellValue(user.getAge());
dataRow.createCell(3).setCellValue(user.getEmail());
}
}
// 自动调整列宽
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
workbook.write(fos);
System.out.println("数据导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用示例:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setId(1);
user1.setName("张三");
user1.setAge(25);
user1.setEmail("zhangsan@example.com");
user1.setRole(Role.NORMAL_USER);
User user2 = new User();
user2.setId(2);
user2.setName("李四");
user2.setAge(30);
user2.setEmail("lisi@example.com");
user2.setRole(Role.ADMIN);
users.add(user1);
users.add(user2);
// 普通用户导出
CustomExportStrategy.export(users, Role.NORMAL_USER, "normal_user_export.xlsx");
// 管理员导出
CustomExportStrategy.export(users, Role.ADMIN, "admin_export.xlsx");
}
}
在上述代码中,CustomExportStrategy类的export方法根据传入的当前用户角色currentRole,对用户数据进行筛选。如果是管理员角色,则导出所有用户数据;如果是普通用户角色,则只导出自己的信息(这里假设普通用户 ID 为 1,实际应用中可根据登录用户信息获取)。通过这种方式,实现了根据不同角色自定义导出策略,满足了多样化的业务需求。
(三)数据校验与清洗
在导出数据之前,对数据进行校验和清洗是非常重要的环节,它可以保证导出的数据质量,避免错误数据或不规范数据对后续分析和使用造成影响。数据校验可以包括数据格式校验(如日期格式是否正确、邮箱格式是否合法等)、数据范围校验(如年龄是否在合理范围内)等;数据清洗可以包括去除重复数据、填充缺失值、纠正错误数据等操作。
以下介绍在导出前对数据进行校验和清洗的方法及代码示例:
假设我们有一个包含用户信息的列表,在导出前需要对用户的年龄进行范围校验,对邮箱格式进行校验,并去除重复的用户数据:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
class User {
private int id;
private String name;
private int age;
private String email;
// 生成Getter和Setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id && age == user.age && Objects.equals(name, user.name) && Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, email);
}
}
public class DataValidationAndCleaning {
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
public static List<User> validateAndCleanData(List<User> users) {
List<User> validUsers = new ArrayList<>();
Set<User> uniqueUsers = new HashSet<>();
for (User user : users) {
if (user.getAge() < 0 || user.getAge() > 120) {
System.err.println("用户 " + user.getName() + " 的年龄不合法,跳过该用户。");
continue;
}
if (!EMAIL_PATTERN.matcher(user.getEmail()).matches()) {
System.err.println("用户 " + user.getName() + " 的邮箱格式不合法,跳过该用户。");
continue;
}
if (uniqueUsers.add(user)) {
validUsers.add(user);
} else {
System.err.println("用户 " + user.getName() + " 是重复数据,跳过该用户。");
}
}
return validUsers;
}
public static void exportToExcel(List<User> users, String filePath) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream(filePath)) {
Sheet sheet = workbook.createSheet("用户数据");
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("邮箱");
// 写入数据
int rowNum = 1;
for (User user : users) {
Row dataRow = sheet.createRow(rowNum++);
dataRow.createCell(0).setCellValue(user.getId());
dataRow.createCell(1).setCellValue(user.getName());
dataRow.createCell(2).setCellValue(user.getAge());
dataRow.createCell(3).setCellValue(user.getEmail());
}
// 自动调整列宽
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
workbook.write(fos);
System.out.println("数据导出成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setId(1);
user1.setName("张三");
user1.setAge(25);
user1.setEmail("zhangsan@example.com");
User user2 = new User();
user2.setId(2);
user2.setName("李四");
user2.setAge(150); // 不合法年龄
user2.setEmail("lisi.example.com"); // 不合法邮箱
User user3 = new User();
user3.setId(1);
user3.setName("张三");
user3.setAge(25);
user3.setEmail("zhangsan@example.com"); // 重复数据
users.add(user1);
users.add(user2);
users.add(user3);
List<User> validUsers = validateAndCleanData(users);
exportToExcel(validUsers, "valid_users_export.xlsx");
}
}
在上述代码中,validateAndCleanData方法对用户数据进行校验和清洗。通过正则表达式校验邮箱格式,通过判断年龄范围校验年龄的合法性,使用HashSet去除重复数据。在main方法中,先对原始用户数据进行校验和清洗,然后将清洗后的数据导出到 Excel 文件中,确保导出的数据是准确、规范且无重复的,提高了数据的可用性。
(四)多语言支持
在国际化的业务场景中,需要导出的 Excel 文件支持多语言显示,以满足不同地区用户的需求。实现多语言支持的关键在于根据用户的语言偏好,动态替换 Excel 文件中的文本内容。
以下说明如何实现导出的 Excel 文件支持多语言显示的方法及代码示例:
假设我们使用资源文件(.properties)来存储不同语言的文本,例如messages_en.properties存储英文文本,messages_zh.properties存储中文文本。定义一个工具类来获取不同语言的文本:
import java.util.Locale;
import java.util.ResourceBundle;
public class I18nUtil {
private static final String BUNDLE_NAME = "messages";
private static ResourceBundle bundle;
public static void setLocale(Locale locale) {
bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
}
public static String getString(String key) {
return bundle.getString(key);
}
}
导出 Excel 的代码如下:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
public class MultilingualExport {
public static void export(Locale locale, String filePath) {
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream(filePath)) {
Sheet sheet = workbook.createSheet("多语言示例");
// 设置语言环境
I18nUtil.setLocale(locale);
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue(I18nUtil.getString("id"));
headerRow.createCell
结语
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~