java easyexcel主子表报表打印实践

 更新时间:2025年09月13日 09:58:04   作者:leijmdas  
EasyExcel支持主子表报表导出,可通过多sheet分开展示或单sheet合并单元格方式实现,后者需自定义策略,适合传统报表形式,根据需求选择合适方案即可

Java EasyExcel 主子表报表打印

EasyExcel 是阿里巴巴开源的一个基于 Java 的简单、省内存的读写 Excel 的工具,特别适合大数据量的 Excel 操作。

下面我将介绍如何使用 EasyExcel 实现主子表结构的报表打印。

1. 添加依赖

首先需要在项目中添加 EasyExcel 依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version> <!-- 使用最新版本 -->
</dependency>

2. 主子表数据结构

假设我们有一个订单(主表)和订单项(子表)的结构:

// 主表数据
public class Order {
    private String orderNo;
    private Date orderDate;
    private String customerName;
    private List<OrderItem> items;
    
    // getters and setters
}

// 子表数据
public class OrderItem {
    private String productName;
    private Integer quantity;
    private BigDecimal price;
    private BigDecimal amount;
    
    // getters and setters
}

3. 实现主子表导出

方法一:使用合并单元格方式

public class OrderExcelExport {
    public static void exportOrders(List<Order> orders, HttpServletResponse response) throws IOException {
        String fileName = "订单报表_" + System.currentTimeMillis() + ".xlsx";
        
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
        
        // 主表sheet
        WriteSheet mainSheet = EasyExcel.writerSheet(0, "订单信息")
                .head(Order.class)
                .build();
        
        // 子表sheet
        WriteSheet itemSheet = EasyExcel.writerSheet(1, "订单明细")
                .head(OrderItem.class)
                .build();
        
        // 写入主表数据
        excelWriter.write(orders, mainSheet);
        
        // 写入子表数据
        for (Order order : orders) {
            excelWriter.write(order.getItems(), itemSheet);
        }
        
        excelWriter.finish();
    }
}

方法二:单个sheet中展示主子表关系

public class OrderExcelExport {
    public static void exportOrdersInOneSheet(List<Order> orders, HttpServletResponse response) throws IOException {
        String fileName = "订单报表_" + System.currentTimeMillis() + ".xlsx";
        
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        
        // 自定义合并策略
        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy();
        
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
                .registerWriteHandler(new OrderMergeStrategy(orders))
                .registerWriteHandler(styleStrategy)
                .build();
        
        WriteSheet sheet = EasyExcel.writerSheet("订单及明细")
                .head(OrderExcelData.class)
                .build();
        
        // 转换数据
        List<OrderExcelData> dataList = convertToExcelData(orders);
        excelWriter.write(dataList, sheet);
        
        excelWriter.finish();
    }
    
    private static List<OrderExcelData> convertToExcelData(List<Order> orders) {
        List<OrderExcelData> result = new ArrayList<>();
        for (Order order : orders) {
            boolean isFirstItem = true;
            for (OrderItem item : order.getItems()) {
                OrderExcelData data = new OrderExcelData();
                if (isFirstItem) {
                    data.setOrderNo(order.getOrderNo());
                    data.setOrderDate(order.getOrderDate());
                    data.setCustomerName(order.getCustomerName());
                    isFirstItem = false;
                }
                data.setProductName(item.getProductName());
                data.setQuantity(item.getQuantity());
                data.setPrice(item.getPrice());
                data.setAmount(item.getAmount());
                result.add(data);
            }
        }
        return result;
    }
}

// 合并策略
public class OrderMergeStrategy implements CellWriteHandler {
    private List<Order> orders;
    private int itemCount = 0;
    
    public OrderMergeStrategy(List<Order> orders) {
        this.orders = orders;
        for (Order order : orders) {
            itemCount += order.getItems().size();
        }
    }
    
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, 
            List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 合并主表信息单元格
        if (cell.getRowIndex() == 1 && !isHead) {
            for (int i = 0; i < orders.size(); i++) {
                Order order = orders.get(i);
                int itemSize = order.getItems().size();
                if (itemSize > 1) {
                    // 合并订单号、日期、客户名等主表信息
                    if (cell.getColumnIndex() == 0) { // 订单号
                        writeSheetHolder.getSheet().addMergedRegionUnsafe(
                            new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + itemSize - 1, 0, 0));
                    } else if (cell.getColumnIndex() == 1) { // 订单日期
                        writeSheetHolder.getSheet().addMergedRegionUnsafe(
                            new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + itemSize - 1, 1, 1));
                    } else if (cell.getColumnIndex() == 2) { // 客户名
                        writeSheetHolder.getSheet().addMergedRegionUnsafe(
                            new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + itemSize - 1, 2, 2));
                    }
                }
            }
        }
    }
}

// Excel数据模型
public class OrderExcelData {
    @ExcelProperty("订单编号")
    private String orderNo;
    
    @ExcelProperty("订单日期")
    @DateTimeFormat("yyyy-MM-dd")
    private Date orderDate;
    
    @ExcelProperty("客户名称")
    private String customerName;
    
    @ExcelProperty("产品名称")
    private String productName;
    
    @ExcelProperty("数量")
    private Integer quantity;
    
    @ExcelProperty("单价")
    private BigDecimal price;
    
    @ExcelProperty("金额")
    private BigDecimal amount;
    
    // getters and setters
}

4. 控制器调用

@RestController
@RequestMapping("/api/order")
public class OrderController {
    
    @GetMapping("/export")
    public void exportOrders(HttpServletResponse response) throws IOException {
        // 模拟数据
        List<Order> orders = getMockOrders();
        
        // 导出Excel
        OrderExcelExport.exportOrdersInOneSheet(orders, response);
    }
    
    private List<Order> getMockOrders() {
        List<Order> orders = new ArrayList<>();
        
        // 订单1
        Order order1 = new Order();
        order1.setOrderNo("ORD20230001");
        order1.setOrderDate(new Date());
        order1.setCustomerName("客户A");
        
        List<OrderItem> items1 = new ArrayList<>();
        OrderItem item1 = new OrderItem();
        item1.setProductName("产品A");
        item1.setQuantity(2);
        item1.setPrice(new BigDecimal("100.00"));
        item1.setAmount(new BigDecimal("200.00"));
        items1.add(item1);
        
        OrderItem item2 = new OrderItem();
        item2.setProductName("产品B");
        item2.setQuantity(1);
        item2.setPrice(new BigDecimal("50.00"));
        item2.setAmount(new BigDecimal("50.00"));
        items1.add(item2);
        
        order1.setItems(items1);
        orders.add(order1);
        
        // 订单2
        Order order2 = new Order();
        order2.setOrderNo("ORD20230002");
        order2.setOrderDate(new Date());
        order2.setCustomerName("客户B");
        
        List<OrderItem> items2 = new ArrayList<>();
        OrderItem item3 = new OrderItem();
        item3.setProductName("产品C");
        item3.setQuantity(3);
        item3.setPrice(new BigDecimal("30.00"));
        item3.setAmount(new BigDecimal("90.00"));
        items2.add(item3);
        
        order2.setItems(items2);
        orders.add(order2);
        
        return orders;
    }
}

5. 高级功能

自定义样式

// 自定义样式策略
public class CustomCellStyleStrategy implements CellWriteHandler {
    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, 
            Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
    }
    
    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, 
            Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
        
        // 标题样式
        if (isHead) {
            CellStyle cellStyle = workbook.createCellStyle();
            Font font = workbook.createFont();
            font.setBold(true);
            font.setColor(IndexedColors.WHITE.getIndex());
            cellStyle.setFont(font);
            cellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex());
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cellStyle.setAlignment(HorizontalAlignment.CENTER);
            cell.setCellStyle(cellStyle);
        }
        
        // 数据行样式
        else {
            CellStyle cellStyle = workbook.createCellStyle();
            cellStyle.setBorderTop(BorderStyle.THIN);
            cellStyle.setBorderBottom(BorderStyle.THIN);
            cellStyle.setBorderLeft(BorderStyle.THIN);
            cellStyle.setBorderRight(BorderStyle.THIN);
            
            // 金额列右对齐
            if (cell.getColumnIndex() >= 5) {
                cellStyle.setAlignment(HorizontalAlignment.RIGHT);
            }
            
            cell.setCellStyle(cellStyle);
        }
    }
    
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, 
            List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
    }
}

然后在导出时注册这个样式策略:

ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
        .registerWriteHandler(new OrderMergeStrategy(orders))
        .registerWriteHandler(new CustomCellStyleStrategy())
        .build();

总结

EasyExcel 提供了灵活的方式来实现主子表结构的报表导出,主要有两种方式:

  1. 使用多个 sheet 分别展示主表和子表数据
  2. 在单个 sheet 中使用合并单元格的方式展示主子表关系

第二种方式更符合传统的报表展示形式,但实现起来稍复杂,需要自定义合并策略。根据实际需求选择合适的方式即可。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 使用Java高效读取Word文档的实战指南

    使用Java高效读取Word文档的实战指南

    在当今的企业级应用中,Word文档作为信息承载和交流的重要载体,其自动化处理需求日益增长,无论是批量数据提取、内容审计,还是文档智能分析,都离不开对Word文档内容的有效读取,本文将深入探讨如何使用Java高效、准确地 读取Word文件,需要的朋友可以参考下
    2025-09-09
  • SpringBoot之如何搭建SpringBoot+Maven项目

    SpringBoot之如何搭建SpringBoot+Maven项目

    这篇文章主要介绍了SpringBoot之如何搭建SpringBoot+Maven项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • SpringBoot 自动配置原理及源码解析

    SpringBoot 自动配置原理及源码解析

    SpringBoot 在项目启动的时候封装了创建对象的方法,无需我们手动配置,接下来通过本文给大家介绍SpringBoot 自动配置原理解析及源码展示,感兴趣的朋友一起看看吧
    2021-06-06
  • Spring IOC与DI核心深入理解

    Spring IOC与DI核心深入理解

    IOC也是Spring的核心之一了,之前学的时候是采用xml配置文件的方式去实现的,后来其中也多少穿插了几个注解,但是没有说完全采用注解实现。那么这篇文章就和大家分享一下,全部采用注解来实现IOC+DI
    2023-02-02
  • mybatis动态SQL if的test写法及规则详解

    mybatis动态SQL if的test写法及规则详解

    这篇文章主要介绍了mybatis动态SQL if的test写法及规则详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • java实现可视化界面肯德基(KFC)点餐系统代码实例

    java实现可视化界面肯德基(KFC)点餐系统代码实例

    这篇文章主要介绍了java肯德基点餐系统,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • mybatis Invalid bound statement(not found)排坑记录

    mybatis Invalid bound statement(not foun

    这篇文章主要介绍了mybatis Invalid bound statement(not found)排坑记录,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • @PathVariable 如何自动填充入实例对象中

    @PathVariable 如何自动填充入实例对象中

    这篇文章主要介绍了@PathVariable 实现自动填充入实例对象中的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • SpringCloud Feign多参数传递及需要注意的问题

    SpringCloud Feign多参数传递及需要注意的问题

    这篇文章主要介绍了SpringCloud Feign多参数传递及需要注意的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java实现八个常用的排序算法:插入排序、冒泡排序、选择排序、希尔排序等

    Java实现八个常用的排序算法:插入排序、冒泡排序、选择排序、希尔排序等

    这篇文章主要介绍了Java如何实现八个常用的排序算法:插入排序、冒泡排序、选择排序、希尔排序 、快速排序、归并排序、堆排序和LST基数排序,需要的朋友可以参考下
    2015-07-07

最新评论