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多线程中断代码详解

    java多线程中断代码详解

    这篇文章主要介绍了java多线程中断代码详解,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • springboot根据实体类生成表的实现方法

    springboot根据实体类生成表的实现方法

    本文介绍了如何通过SpringBoot工程引入SpringDataJPA,并通过实体类自动生成数据库表的过程,包括常见问题解决方法,感兴趣的可以了解一下
    2024-09-09
  • Spring Validation数据校验详解

    Spring Validation数据校验详解

    SpringValidation是Spring框架的数据校验抽象层,支持注解、分组、嵌套校验及国际化,默认集成HibernateValidator,可自定义校验器满足复杂需求,适用于Web参数校验和业务逻辑验证
    2025-09-09
  • IntelliJ IDEA的build path设置方法

    IntelliJ IDEA的build path设置方法

    这篇文章主要介绍了IntelliJ IDEA的build path设置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • java中动态代理如何实现详解

    java中动态代理如何实现详解

    动态代理是基于接口实现的代理,mybatis就是用这个技术实现的,下面这篇文章主要给大家介绍了关于java中动态代理如何实现的相关资料,需要的朋友可以参考下
    2024-01-01
  • java实现ssh登录linux并执行命令的三种实现方式

    java实现ssh登录linux并执行命令的三种实现方式

    文章介绍了三种在Java中实现SSH登录Linux并执行命令的方法,包括使用ganymed-ssh2、jsch和sshd-core,由于ganymed-ssh2和jsch的最新版本较旧,可能无法与较新的Linux系统兼容,而sshd-core一直在更新,推荐使用
    2024-11-11
  • SpringBoot接收数组参数和集合参数方式

    SpringBoot接收数组参数和集合参数方式

    这篇文章主要介绍了SpringBoot接收数组参数和集合参数方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03
  • Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解

    Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解

    这篇文章主要介绍了Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • 解决SpringMVC同时接收Json和Restful时Request里有Map的问题

    解决SpringMVC同时接收Json和Restful时Request里有Map的问题

    今天小编就为大家分享一篇解决SpringMVC同时接收Json和Restful时Request里有Map的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • java多线程应用实现方法

    java多线程应用实现方法

    以前没有写笔记的习惯,现在慢慢的发现及时总结是多么的重要了,呵呵。虽然才大二,但是也快要毕业了,要加油
    2012-11-11

最新评论