使用EasyPOI实现Java订单数据导出(含多物料信息)——模板语法详解与实战

在企业级Java开发中,数据导出为Excel是常见的需求。尤其在订单管理系统中,往往需要将一个订单及其包含的多个物料信息导出为结构清晰的Excel表格。本文将详细介绍如何使用 EasyPOI 实现这一功能,重点讲解其强大的模板语法表达式语言,并通过完整代码示例带你从零实现订单+多物料导出。


一、EasyPOI 简介

EasyPOI 是基于 Apache POI 的封装库,极大简化了 Java 操作 Excel 的复杂度。它支持:

  • 简单对象导出
  • 模板导出(支持表达式)
  • 多Sheet导出
  • 图片导出
  • 合并单元格等高级功能

本文重点使用 模板导出功能(TemplateExportService) ,通过 .xlsx 模板文件定义样式和结构,结合 EasyPOI 的表达式语言动态填充数据。


二、需求分析:订单+物料导出

我们有一个订单(Order),每个订单包含多个物料(Material)。需要导出如下结构的Excel:

订单编号 订单名称 客户名称 下单日期
O20250731001 采购订单 某某公司 2025-07-31
物料编号 物料名称 数量 单价
M001 螺丝钉 100 0.5
M002 垫片 200 0.3
合计

要求:订单信息在上,物料列表在下,自动合并订单信息行,金额合计自动计算。


三、EasyPOI 模板语法表达式详解

EasyPOI 支持在Excel模板中使用 ${} 语法进行数据绑定和逻辑控制。常用表达式如下:

1. 基本变量引用

bash 复制代码
${order.orderNo}
${order.customerName}
${order.orderDate}

2. 集合循环(核心!)

使用 fe:for 语法遍历集合:

ruby 复制代码
{{fe:for mat in order.materials}}
  ${mat.materialNo}
  ${mat.materialName}
  ${mat.quantity}
  ${mat.unitPrice}
  ${mat.amount}
{{fe:end for}}
  • fe:for 变量 in 集合路径:开始循环
  • fe:end for:结束循环
  • 可嵌套使用

3. 条件判断

css 复制代码
{{fe:if order.status == 'PAID'}}
  已付款
{{fe:else}}
  未付款
{{fe:end if}}

4. 函数调用(内置函数)

xml 复制代码
${fe:formatDate(order.orderDate, "yyyy-MM-dd")}  <!-- 格式化日期 -->
${fe:currency(order.totalAmount)}                <!-- 货币格式 -->
${fe:sum(order.materials, "amount")}             <!-- 求和 -->

5. 单元格合并控制

通过 fe:merge 实现自动合并:

sql 复制代码
{{fe:merge size="4" start="1" direction="down"}}
  ${order.orderNo}
{{fe:end merge}}

表示向下合并4行(包括当前行)


四、模板设计(order_template.xlsx)

resources/templates/ 下创建 order_template.xlsx,设计如下:

A B C D E
订单编号 ${order.orderNo} 客户名称 ${order.customerName}
订单名称 ${order.orderName} 下单日期 ${fe:formatDate(order.orderDate, "yyyy-MM-dd")}
物料编号 物料名称 数量 单价 金额
{{fe:for mat in order.materials}}
${mat.materialNo} ${mat.materialName} ${mat.quantity} ${mat.unitPrice} ${mat.amount}
{{fe:end for}}
合计 ${fe:sum(order.materials, "amount")}

注意:合并订单信息行可通过 fe:merge 实现,此处简化。


五、Java代码实现

1. 引入Maven依赖

xml 复制代码
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>4.4.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-web</artifactId>
    <version>4.4.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-annotation</artifactId>
    <version>4.4.0</version>
</dependency>

2. 定义实体类

kotlin 复制代码
// Material.java
public class Material {
    private String materialNo;
    private String materialName;
    private Integer quantity;
    private Double unitPrice;
    private Double amount; // quantity * unitPrice

    // 构造器、Getter、Setter 省略
}
java 复制代码
// Order.java
import java.util.Date;
import java.util.List;

public class Order {
    private String orderNo;
    private String orderName;
    private String customerName;
    private Date orderDate;
    private List<Material> materials;

    // 构造器、Getter、Setter 省略
}

3. 导出服务类

java 复制代码
// OrderExportService.java
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
import cn.afterturn.easypoi.view.PoiBaseView;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

@Service
public class OrderExportService {

    public void exportOrder(Order order, HttpServletResponse response) {
        // 1. 配置模板路径
        String templatePath = "templates/order_template.xlsx";
        File templateFile = new File(this.getClass().getClassLoader().getResource(templatePath).getFile());

        // 2. 设置模板参数
        TemplateExportParams params = new TemplateExportParams();
        params.setTemplateUrl(templateFile.getAbsolutePath());

        // 3. 准备数据模型
        Map<String, Object> map = new HashMap<>();
        map.put("order", order); // 数据绑定到模板中的 ${order.xxx}

        try {
            // 4. 执行导出
            ModelAndView mv = new ModelAndView(new PoiBaseView());
            mv.addObject(PoiBaseView.TEMPLATE_URL, templatePath);
            mv.addObject(PoiBaseView.DATA_MAP, map);
            mv.addObject(PoiBaseView.PARAMS, params);
            mv.addObject(PoiBaseView.FILE_NAME, "订单_" + order.getOrderNo());

            // 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-Disposition", "attachment; filename=" + new String(("订单_" + order.getOrderNo() + ".xlsx").getBytes(), "ISO8859-1"));

            // 使用 PoiBaseView 渲染并输出
            PoiBaseView.render(mv, request, response);
        } catch (Exception e) {
            e.printStackTrace();
            // 处理异常
        }
    }
}

注意:PoiBaseView 是 EasyPOI 提供的 Spring MVC 视图类,需确保 Spring 环境。

4. 控制器调用

less 复制代码
// OrderController.java
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderExportService exportService;

    @GetMapping("/export/{id}")
    public void exportOrder(@PathVariable String id, HttpServletResponse response) {
        // 模拟查询订单数据
        Order order = buildMockOrder(id);

        exportService.exportOrder(order, response);
    }

    private Order buildMockOrder(String id) {
        Order order = new Order();
        order.setOrderNo("O20250731001");
        order.setOrderName("采购订单");
        order.setCustomerName("某某公司");
        order.setOrderDate(new Date());

        List<Material> materials = Arrays.asList(
            new Material("M001", "螺丝钉", 100, 0.5, 50.0),
            new Material("M002", "垫片", 200, 0.3, 60.0)
        );
        order.setMaterials(materials);

        return order;
    }
}

六、关键点分析

1. 模板路径问题

  • 模板文件应放在 resources 目录下,使用 ClassLoader 加载。
  • 确保路径正确,避免 FileNotFoundException

2. 表达式语法严格性

  • ${} 中的变量名必须与 Java 对象属性一致(区分大小写)。
  • 集合循环必须配对 {{fe:for}}{{fe:end for}}

3. 自动合并单元格

若需合并订单信息行(如订单编号跨2行),可在模板中:

sql 复制代码
{{fe:merge size="2" start="0" direction="down"}}
  ${order.orderNo}
{{fe:end merge}}

4. 金额合计

使用 ${fe:sum(order.materials, "amount")} 自动计算物料金额总和,无需在Java中额外计算。


七、常见问题与解决方案

问题 原因 解决方案
模板找不到 路径错误 使用 getClassLoader().getResource()
数据未填充 变量名不匹配 检查 ${} 中的字段名
循环不生效 fe:for 语法错误 检查开始/结束标签是否配对
合并失败 fe:merge 参数错误 检查 sizedirection

八、总结

通过 EasyPOI 的模板导出功能,我们可以:

  • 分离样式与逻辑:模板定义样式,Java处理数据。
  • 提升开发效率:无需手动创建单元格、设置样式。
  • 支持复杂结构:轻松实现一对多数据导出。
  • 灵活控制:通过表达式语言实现循环、条件、计算等。

EasyPOI 的模板语法虽然简单,但功能强大,特别适合订单、报表等结构化数据导出场景。掌握其表达式语言是高效开发的关键。

欢迎关注我的技术博客,持续分享Java实战经验!

相关推荐
SimonKing5 分钟前
营销级二维码生成术:Java如何打造专属标识
java·后端·程序员
种时光的人9 分钟前
JVM面试通关指南:内存区域、类加载器、双亲委派与GC算法全解析
java·开发语言·jvm·面试
_风不会停息9 分钟前
JDK21 虚拟线程的实现原理和应用
java·后端
打野二师兄1 小时前
网关 + MDC 过滤器方案,5分钟集成 日志 traceid
java·开发语言
三原1 小时前
6年前端学习Java Spring boot 要怎么学?
java·前端·javascript
励志成为糕手1 小时前
编程语言Java——核心技术篇(六)解剖反射:性能的代价还是灵活性的福音?
java·开发语言·intellij-idea
yourkin6661 小时前
Bean Post-Processor
java·开发语言·前端
qq_266348731 小时前
idea 源码阅读笔记
java·笔记·intellij-idea
飞翔的佩奇1 小时前
Java项目:基于SSM框架实现的小区物业管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告+任务书+远程部署】
java·数据库·mysql·毕业设计·jsp·ssm框架·小区物业管理系统
天天开心(∩_∩)1 小时前
代码随想录算法训练营第三十七天
java·开发语言·算法