当前位置: 首页 > news >正文

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

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


一、EasyPOI 简介

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

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

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


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

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

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

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


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

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

1. 基本变量引用

${order.orderNo}
${order.customerName}
${order.orderDate}

2. 集合循环(核心!)

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

{{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. 条件判断

{{fe:if order.status == 'PAID'}}已付款
{{fe:else}}未付款
{{fe:end if}}

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

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

5. 单元格合并控制

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

{{fe:merge size="4" start="1" direction="down"}}${order.orderNo}
{{fe:end merge}}

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


四、模板设计(order_template.xlsx)

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

ABCDE
订单编号${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依赖

<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. 定义实体类

// Material.java
public class Material {private String materialNo;private String materialName;private Integer quantity;private Double unitPrice;private Double amount; // quantity * unitPrice// 构造器、Getter、Setter 省略
}
// 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. 导出服务类

// 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. 控制器调用

// OrderController.java
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate 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行),可在模板中:

{{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 参数错误检查 size 和 direction

八、总结

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

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

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

http://www.dtcms.com/a/308384.html

相关文章:

  • Redis实战(4)-- BitMap结构与使用
  • uvicorn 启动重复加载 多次加载
  • 【LeetCode 热题 100】4. 寻找两个正序数组的中位数——(解法一)线性扫描
  • C++(模板)
  • java笔记——ConcurrentLinkedQueue
  • AUTOSAR进阶图解==>AUTOSAR_SRS_FunctionInhibitionManager
  • axios封装对比
  • 记录自己使用gitee和jenkins
  • PHP反序列化学习+解析+复现
  • 提升Windows操作效率:三款实用辅助工具功能解析​
  • 代码随想录Day35:动态规划(背包问题 二维 一维、分割等和子集)
  • Spring Boot整合MyBatis-Plus全攻略
  • 如何监控项目的每个阶段,提高执行效率
  • SchemaCrawler:一款免费开源的数据库文档工具
  • 斐波那契数
  • AI学习笔记三十三:基于Opencv的单目标跟踪
  • OpenCSG月度更新2025.7
  • leecode18 四数之和
  • 个股期权合约期内遇到标的停牌,如何处置?
  • DoRA详解:从LoRA到权重分解的进化
  • Redis线程模型讨论
  • 修改VSCode远程SSH的PATH
  • JVM字节码文件结构剖析
  • JVM学习日记(十二)Day12
  • 解释 MySQL 中的 EXPLAIN 命令的作用和使用场景
  • 格雷希尔G25F系列电气端口快速封堵接头,解决电池包、电机控制器等壳体的气密性测试难题,提升效率与可靠性,助力新能源汽车生产。
  • ARM--中断
  • 三坐标测量仪攻克深孔检测!破解新能源汽车阀体阀孔测量难题
  • 雷霆战机游戏代码
  • ABS系统专用磁阻式汽车轮速传感器