乐于分享
好东西不私藏

一键操作 Word 表格:Hutool TableUtil 从入门到实战

一键操作 Word 表格:Hutool TableUtil 从入门到实战

在企业应用开发中,生成 Word 格式的报表、合同、审批单是常见需求。Apache POI 虽然功能强大,但操作 Word 表格的 API 相对繁琐,需要手动处理行列创建、数据填充等细节。Hutool 的 TableUtil 对 POI 的表格操作进行了封装,让 Word 表格的创建和数据填充变得简单高效。

核心功能

TableUtil 专注于 Word 文档(docx 格式)中的表格操作,提供以下能力:

  1. 1. 快速创建表格:支持空表格和带数据的表格创建
  2. 2. 智能行列管理:自动获取或创建行和单元格,避免索引越界
  3. 3. 灵活数据填充:支持集合、Map、JavaBean 等多种数据源
  4. 4. 简化代码逻辑:减少样板代码,提升开发效率

常用 API

1. 创建空表格

最基础的表格创建方式,生成一个只有一行的空表格。

import cn.hutool.poi.word.TableUtil;import org.apache.poi.xwpf.usermodel.XWPFDocument;import org.apache.poi.xwpf.usermodel.XWPFTable;import java.io.FileOutputStream;// 创建 Word 文档对象XWPFDocument doc = new XWPFDocument();// 创建空表格(默认只有一行)XWPFTable table = TableUtil.createTable(doc);// 保存文档try (FileOutputStream out = new FileOutputStream("empty_table.docx")) {    doc.write(out);}

这个方法适用于需要手动控制表格结构的场景,比如复杂的表头设计或动态列数的表格。

2. 创建表格并填充数据

直接传入数据集合,TableUtil 会自动创建表格并填充内容。

import cn.hutool.core.collection.CollUtil;// 准备数据(List 集合)List<List<String>> data = CollUtil.newArrayList(    CollUtil.newArrayList("姓名", "部门", "职位"),    CollUtil.newArrayList("张三", "技术部", "Java 工程师"),    CollUtil.newArrayList("李四", "产品部", "产品经理"),    CollUtil.newArrayList("王五", "运营部", "运营专员"));XWPFDocument doc = new XWPFDocument();// 创建表格并填充数据XWPFTable table = TableUtil.createTable(doc, data);try (FileOutputStream out = new FileOutputStream("employee_table.docx")) {    doc.write(out);}

这种方式特别适合数据结构简单、格式统一的表格,比如员工名单、商品清单等。

3. 获取或创建行

在动态构建表格时,经常需要在指定位置插入数据。getOrCreateRow 方法会智能判断:如果行已存在则直接返回,不存在则自动创建。

XWPFDocument doc = new XWPFDocument();XWPFTable table = TableUtil.createTable(doc);// 获取或创建第 0 行(已存在,直接返回)XWPFTableRow row0 = TableUtil.getOrCreateRow(table, 0);// 获取或创建第 1 行(不存在,自动创建)XWPFTableRow row1 = TableUtil.getOrCreateRow(table, 1);// 获取或创建第 5 行(跳过 2-4 行,自动创建所有缺失的行)XWPFTableRow row5 = TableUtil.getOrCreateRow(table, 5);System.out.println("表格总行数: " + table.getRows().size());// 输出: 表格总行数: 6

这个方法的优势在于避免了手动判断行是否存在,也不用担心索引越界异常。

4. 获取或创建单元格

与获取行类似,getOrCreateCell 方法用于获取或创建指定列的单元格。

XWPFDocument doc = new XWPFDocument();XWPFTable table = TableUtil.createTable(doc);XWPFTableRow row = table.getRow(0);// 获取或创建第 0 列单元格XWPFTableCell cell0 = TableUtil.getOrCreateCell(row, 0);cell0.setText("姓名");// 获取或创建第 1 列单元格XWPFTableCell cell1 = TableUtil.getOrCreateCell(row, 1);cell1.setText("年龄");// 获取或创建第 2 列单元格XWPFTableCell cell2 = TableUtil.getOrCreateCell(row, 2);cell2.setText("部门");

配合 getOrCreateRow 使用,可以灵活地在表格任意位置插入数据。

5. 写入行数据(集合方式)

writeRow 方法支持多种数据源,最简单的是传入集合。

XWPFDocument doc = new XWPFDocument();XWPFTable table = TableUtil.createTable(doc);// 写入表头XWPFTableRow headerRow = table.getRow(0);List<String> headers = CollUtil.newArrayList("编号", "商品名称", "单价", "库存");TableUtil.writeRow(headerRow, headers);// 写入数据行XWPFTableRow dataRow = TableUtil.getOrCreateRow(table, 1);List<Object> rowData = CollUtil.newArrayList("P001", "咖啡豆", 89.9, 500);TableUtil.writeRow(dataRow, rowData);try (FileOutputStream out = new FileOutputStream("product_table.docx")) {    doc.write(out);}

6. 写入行数据(Map 方式)

当数据源是 Map 时,可以选择是否将 Key 作为表头写入。

XWPFDocument doc = new XWPFDocument();XWPFTable table = TableUtil.createTable(doc);// 准备 Map 数据Map<String, Object> userData = new LinkedHashMap<>();userData.put("姓名", "张三");userData.put("年龄", 28);userData.put("职位", "Java 工程师");userData.put("薪资", 15000);// 写入数据,第一行作为表头(isWriteKeyAsHead = true)XWPFTableRow row0 = table.getRow(0);TableUtil.writeRow(row0, userData, true);// 写入第二行数据,不写入 Key(isWriteKeyAsHead = false)Map<String, Object> userData2 = new LinkedHashMap<>();userData2.put("姓名", "李四");userData2.put("年龄", 30);userData2.put("职位", "产品经理");userData2.put("薪资", 18000);XWPFTableRow row1 = TableUtil.getOrCreateRow(table, 1);TableUtil.writeRow(row1, userData2, false);

注意:使用 LinkedHashMap 可以保证 Key 的顺序,避免列顺序混乱。

7. 写入行数据(JavaBean 方式)

对于 JavaBean 对象,TableUtil 会自动提取字段值填充到表格中。

// 定义 JavaBean@Datapublic class Employee {    private String name;    private Integer age;    private String department;    private Double salary;}// 使用示例XWPFDocument doc = new XWPFDocument();XWPFTable table = TableUtil.createTable(doc);Employee emp = new Employee();emp.setName("王五");emp.setAge(25);emp.setDepartment("技术部");emp.setSalary(12000.0);// 写入数据,第一行作为表头XWPFTableRow row = table.getRow(0);TableUtil.writeRow(row, emp, true);

这种方式特别适合从数据库查询出实体对象后直接导出到 Word 文档。

8. 批量填充表格数据

writeTable 方法可以一次性将整个数据集合填充到表格中。

XWPFDocument doc = new XWPFDocument();// 准备数据List<List<String>> data = CollUtil.newArrayList(    CollUtil.newArrayList("订单号", "客户", "金额", "状态"),    CollUtil.newArrayList("ORD001", "张三", "1580.00", "已完成"),    CollUtil.newArrayList("ORD002", "李四", "2300.00", "待发货"),    CollUtil.newArrayList("ORD003", "王五", "890.00", "已取消"));// 先创建空表格XWPFTable table = TableUtil.createTable(doc);// 批量填充数据TableUtil.writeTable(table, data);try (FileOutputStream out = new FileOutputStream("order_table.docx")) {    doc.write(out);}

这个方法内部会自动处理行的创建和数据填充,是最便捷的批量操作方式。

实战场景:生成月度销售报表

在咖啡店管理系统中,每月需要生成销售报表提交给管理层。报表包含商品销售统计、员工业绩排名等信息。使用 TableUtil 可以快速将数据库查询结果导出为 Word 文档。

import cn.hutool.poi.word.TableUtil;import org.apache.poi.xwpf.usermodel.*;import org.springframework.stereotype.Service;import java.io.FileOutputStream;import java.math.BigDecimal;import java.time.LocalDate;import java.time.format.DateTimeFormatter;import java.util.ArrayList;import java.util.List;@Servicepublic class SalesReportService {    /**     * 生成月度销售报表     */    public void generateMonthlySalesReport(int year, int month) throws Exception {        // 创建 Word 文档        XWPFDocument doc = new XWPFDocument();        // 添加标题        XWPFParagraph title = doc.createParagraph();        title.setAlignment(ParagraphAlignment.CENTER);        XWPFRun titleRun = title.createRun();        titleRun.setText(year + "年" + month + "月销售报表");        titleRun.setFontSize(18);        titleRun.setBold(true);        // 添加生成时间        XWPFParagraph datePara = doc.createParagraph();        XWPFRun dateRun = datePara.createRun();        dateRun.setText("生成时间:" + LocalDate.now().format(            DateTimeFormatter.ofPattern("yyyy-MM-dd")));        dateRun.setFontSize(10);        // 空行        doc.createParagraph();        // 第一部分:商品销售统计        XWPFParagraph section1 = doc.createParagraph();        XWPFRun section1Run = section1.createRun();        section1Run.setText("一、商品销售统计");        section1Run.setFontSize(14);        section1Run.setBold(true);        // 从数据库查询商品销售数据(模拟)        List<ProductSales> productSalesList = queryProductSales(year, month);        // 构建表格数据        List<List<Object>> productData = new ArrayList<>();        productData.add(List.of("排名", "商品名称", "销售数量", "销售金额", "占比"));        int rank = 1;        for (ProductSales sales : productSalesList) {            productData.add(List.of(                rank++,                sales.getProductName(),                sales.getQuantity(),                sales.getAmount().toString(),                sales.getPercentage() + "%"            ));        }        // 创建商品销售表格        XWPFTable productTable = TableUtil.createTable(doc);        TableUtil.writeTable(productTable, productData);        // 设置表格样式        productTable.setWidth("100%");        // 空行        doc.createParagraph();        // 第二部分:员工业绩排名        XWPFParagraph section2 = doc.createParagraph();        XWPFRun section2Run = section2.createRun();        section2Run.setText("二、员工业绩排名");        section2Run.setFontSize(14);        section2Run.setBold(true);        // 从数据库查询员工业绩数据(模拟)        List<EmployeePerformance> performanceList = queryEmployeePerformance(year, month);        // 构建员工业绩表格        XWPFTable empTable = TableUtil.createTable(doc);        // 写入表头        XWPFTableRow headerRow = empTable.getRow(0);        List<String> headers = List.of("排名", "员工姓名", "接单数", "销售额", "客户评分");        TableUtil.writeRow(headerRow, headers);        // 逐行写入员工数据        int empRank = 1;        for (EmployeePerformance perf : performanceList) {            XWPFTableRow dataRow = TableUtil.getOrCreateRow(empTable, empRank);            List<Object> rowData = List.of(                empRank,                perf.getEmployeeName(),                perf.getOrderCount(),                perf.getSalesAmount().toString(),                perf.getRating()            );            TableUtil.writeRow(dataRow, rowData);            empRank++;        }        // 设置表格样式        empTable.setWidth("100%");        // 空行        doc.createParagraph();        // 第三部分:总结        XWPFParagraph summary = doc.createParagraph();        XWPFRun summaryRun = summary.createRun();        summaryRun.setText("三、数据总结");        summaryRun.setFontSize(14);        summaryRun.setBold(true);        // 计算汇总数据        BigDecimal totalAmount = productSalesList.stream()            .map(ProductSales::getAmount)            .reduce(BigDecimal.ZERO, BigDecimal::add);        int totalOrders = performanceList.stream()            .mapToInt(EmployeePerformance::getOrderCount)            .sum();        // 创建汇总表格        XWPFTable summaryTable = TableUtil.createTable(doc);        List<List<String>> summaryData = List.of(            List.of("指标", "数值"),            List.of("总销售额", totalAmount.toString() + " 元"),            List.of("总订单数", totalOrders + " 单"),            List.of("客单价", totalAmount.divide(                BigDecimal.valueOf(totalOrders), 2, BigDecimal.ROUND_HALF_UP            ).toString() + " 元")        );        TableUtil.writeTable(summaryTable, summaryData);        // 保存文档        String fileName = String.format("销售报表_%d年%d月.docx", year, month);        try (FileOutputStream out = new FileOutputStream(fileName)) {            doc.write(out);        }        System.out.println("报表生成成功:" + fileName);    }    // 模拟数据库查询方法    private List<ProductSales> queryProductSales(int year, int month) {        List<ProductSales> list = new ArrayList<>();        list.add(new ProductSales("美式咖啡", 320, new BigDecimal("9600.00"), "35.2"));        list.add(new ProductSales("拿铁咖啡", 280, new BigDecimal("11200.00"), "41.0"));        list.add(new ProductSales("卡布奇诺", 150, new BigDecimal("6000.00"), "22.0"));        list.add(new ProductSales("摩卡咖啡", 50, new BigDecimal("2500.00"), "9.2"));        return list;    }    private List<EmployeePerformance> queryEmployeePerformance(int year, int month) {        List<EmployeePerformance> list = new ArrayList<>();        list.add(new EmployeePerformance("张三", 180, new BigDecimal("18000.00"), 4.8));        list.add(new EmployeePerformance("李四", 150, new BigDecimal("15500.00"), 4.6));        list.add(new EmployeePerformance("王五", 120, new BigDecimal("12000.00"), 4.5));        return list;    }}// 商品销售数据实体@Dataclass ProductSales {    private String productName;    private Integer quantity;    private BigDecimal amount;    private String percentage;    public ProductSales(String productName, Integer quantity,                       BigDecimal amount, String percentage) {        this.productName = productName;        this.quantity = quantity;        this.amount = amount;        this.percentage = percentage;    }}// 员工业绩数据实体@Dataclass EmployeePerformance {    private String employeeName;    private Integer orderCount;    private BigDecimal salesAmount;    private Double rating;    public EmployeePerformance(String employeeName, Integer orderCount,                              BigDecimal salesAmount, Double rating) {        this.employeeName = employeeName;        this.orderCount = orderCount;        this.salesAmount = salesAmount;        this.rating = rating;    }}

运行效果:

生成的 Word 文档包含三个部分:

  1. 1. 商品销售统计表:展示各商品的销售数量、金额和占比
  2. 2. 员工业绩排名表:展示员工的接单数、销售额和客户评分
  3. 3. 数据总结表:汇总总销售额、总订单数和客单价

这个实战案例展示了 TableUtil 的几个关键用法:

  • • 使用 createTable 和 writeTable 快速创建带数据的表格
  • • 使用 getOrCreateRow 和 writeRow 逐行填充数据
  • • 结合 POI 的原生 API 设置表格样式和文档格式
  • • 处理复杂的业务数据结构(集合、实体类)

使用技巧

1. 数据源选择

根据数据结构选择合适的方法:

  • • 简单二维数据:使用 List<List<?>> + writeTable
  • • 键值对数据:使用 Map + writeRow(适合动态列)
  • • 实体对象:使用 JavaBean + writeRow(适合固定结构)

2. 表格样式设置

TableUtil 专注于数据填充,样式设置需要结合 POI 原生 API:

// 设置表格宽度table.setWidth("100%");// 设置单元格背景色XWPFTableCell cell = row.getCell(0);cell.setColor("F0F0F0");// 设置单元格文本对齐XWPFParagraph para = cell.getParagraphs().get(0);para.setAlignment(ParagraphAlignment.CENTER);

3. 性能优化

处理大量数据时的建议:

  • • 避免频繁创建文档对象,复用 XWPFDocument
  • • 批量操作优先使用 writeTable,减少方法调用次数
  • • 大数据量时考虑分页生成多个文档

4. 异常处理

文件操作需要妥善处理异常:

try (FileOutputStream out = new FileOutputStream("report.docx")) {    doc.write(out);} catch (IOException e) {    log.error("文档生成失败", e);    throw new BusinessException("报表导出失败,请稍后重试");}

总结

TableUtil 是 Hutool 针对 Word 表格操作的实用工具类,它的价值在于:

  1. 1. 简化代码:将繁琐的行列创建逻辑封装成简洁的方法调用
  2. 2. 提升效率:支持多种数据源,减少数据转换的工作量
  3. 3. 降低出错:智能的 getOrCreate 机制避免索引越界异常
  4. 4. 易于维护:代码可读性强,后续修改成本低

在需要生成 Word 报表、合同、审批单等场景中,TableUtil 配合 POI 可以显著提升开发效率。掌握这个工具类,能让你在处理文档导出需求时更加从容。

运行效果

END

你的关注将是我持续更新的动力
↓↓↓