JQuick-Excel JQuickRow 数据准备实战:JObjectConverter、JQuickRow.toRows 与 XML 导出链路
tags: #JQuickExcel #Java #JQuickRow #JObjectConverter #Excel导出
简介
本文将说明如何将 List<Map<String, Object>> 按 JObjectConverter.convert(data) 和 JQuickRow.toRows(...) 的公开路径准备为导出行,并交给 JQuickExcelExportXmlParseFactory 与 JQuickXmlFactory 执行 XML 规则。
前言
Excel 导出通常从业务查询结果开始,而不是从单元格坐标开始。订单服务可能得到订单对象,学生服务可能得到学生记录,报表服务可能得到 Map 列表。无论数据起点是什么,XML 规则最终需要能依据字段名读取每行值。JQuick-Excel 的 README 给出了一条明确的准备方式:先组织 List<Map<String, Object>>,然后执行 JObjectConverter.convert(data),最后调用 JQuickRow.toRows(...) 获得 List<JQuickRow>。
这条链路值得单独说明,因为它划分了职责。Java 负责从业务层取得数据、确定字段和值;XML 负责 SHEET、HEADER、MAPPING、FORMAT、TRANSFORM 等工作簿规则;JQuickExcelExportXmlParseFactory 接收行数据和输出流;JQuickXmlFactory 加载 XML 并创建接口代理。把这些边界分清后,字段变更、标题变更、格式变更不会都堆到一个 POI 循环中。
本文只使用 README 已出现的 Map 列表、JObjectConverter.convert、JQuickRow.toRows 和 XML 导出代理写法。不会声称 JQuickRow 的私有存储结构,不会编造任意对象、嵌套属性、反射字段或空值的自动策略。若业务层使用实体对象,应先在项目自己的业务代码中明确整理为文中所用的数据字段,再沿公开示例转换。
以学生导出为例,Java 准备 id、name、age,XML 将它们映射成"主键、姓名、年龄"。重点不在 Map 的插入顺序,而在数据键与导出 MAPPING 左侧字段一致。列展示顺序由 XML 显式定义,因此模板审查不依赖集合迭代的偶然结果。
环境与依赖
README-CN.md 指出 JQuick-Excel 的运行环境是 Java 8+,支持 xls 和 xlsx。本文采用 README 中的 Maven 坐标和版本。jquick-excel.xml 应位于 src/main/resources,供 JQuickXmlFactory 按资源名加载。
xml
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-excel</artifactId>
<version>3.6.0</version>
</dependency>
准备数据前应先确认 XML 规则。本文 XML 的导出映射左侧是 id、name、age,所以 Java Map 必须提供同名键。输出流指向可写文件,例如 students.xlsx。服务接口的全限定名必须等于 XML namespace,接口的导出方法名必须等于 <excel name>。
这些前提能让排错更直接:如果标题不对,检查 XML 的 MAPPING 右侧;如果某列数据为空,检查 Java Map 键和 MAPPING 左侧;如果规则完全没有执行,检查资源、namespace 和方法名;如果输出文件不能写入,再检查输出流及其生命周期。
代码示例
先定义 XML。测试资源使用 EXPORT WITH、SHEET="学生表"、HEADER=true 和 MAPPING。本文沿用相同 DSL 结构,仅使用三列以突出数据准备。注意 XML 的字段名是 id、name、age,标题分别是"主键、姓名、年龄"。
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.RowExcelService">
<excel name="exportStudents" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="学生表",
HEADER=true,
MAPPING={
"id":"主键",
"name":"姓名",
"age":"年龄"
}
]]>
</excel>
</excels>
接口采用 README 中展示的代理调用形态。它不接收 List<JQuickRow> 作为方法参数,因为 README 示例将行数据在创建导出解析器时传入;接口参数仍是 XML 示例里的 field、value。
java
import com.github.paohaijiao.xml.param.Param;
public interface RowExcelService {
void exportStudents(@Param("field") String field, @Param("value") String value);
}
下面是完整的数据准备和导出调用。第一段通过 ArrayList 放入多条记录,每一条都使用 Map 表示字段和值。第二段是关键:JObjectConverter.convert(students) 的结果交给 JQuickRow.toRows(...),得到 List<JQuickRow>。第三段把行列表和输出流传入 JQuickExcelExportXmlParseFactory,再通过 JQuickXmlFactory 获取服务代理并调用 XML 方法。
java
import com.github.paohaijiao.convert.JObjectConverter;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.JQuickFactory;
import com.github.paohaijiao.xml.JQuickXmlFactory;
import com.github.paohaijiao.xml.parse.JQuickParseHandler;
import com.github.paohaijiao.xml.parse.excel.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
List<Map<String, Object>> students = new ArrayList<>();
Map<String, Object> first = new LinkedHashMap<>();
first.put("id", "1001");
first.put("name", "Alice");
first.put("age", 20);
students.add(first);
Map<String, Object> second = new LinkedHashMap<>();
second.put("id", "1002");
second.put("name", "Bob");
second.put("age", 21);
students.add(second);
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(students));
try (OutputStream output = new FileOutputStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
RowExcelService service = factory.createApi(RowExcelService.class);
service.exportStudents("field", "value");
}
当需要导出测试资源中出现的更多字段时,仍然遵循同一规则。测试 XML 的 MAPPING 左侧包含 id、name、gender、age、enrollmentDate、className、ignoreField,则对应的 Java 数据应提供这些同名键。是否使用 TRANSFORM 转换 gender、age 或 enrollmentDate,由 XML 规则决定,不能替代基础字段准备。
java
Map<String, Object> student = new LinkedHashMap<>();
student.put("id", "1001");
student.put("name", "Alice");
student.put("gender", "1");
student.put("age", 20);
student.put("enrollmentDate", new java.util.Date());
student.put("className", "Class A");
student.put("ignoreField", false);
上例仅展示与测试 XML 左侧对应的键名,不在这里断言每种 Java 值会采用何种内部转换规则。对于日期显示、字典翻译或算术转换,应以 XML 中已写明的 FORMAT 或 TRANSFORM 规则和真实集成测试为准。
原理说明
JObjectConverter.convert(data) 与 JQuickRow.toRows(...) 是 README 导出示例明确给出的两步转换。本文无需猜测转换中间对象的私有实现,只需遵循公开调用顺序:业务数据先被转换,再被包装为导出解析器需要的 List<JQuickRow>。这使 XML 层可以基于字段名访问当前行。
导出执行时,JQuickExcelExportXmlParseFactory 已经持有 rows 与 output。JQuickXmlFactory 读取 XML 后,createApi(RowExcelService.class) 返回代理;调用 exportStudents 会路由到 <excel name="exportStudents"> 的 EXPORT WITH。规则中的 MAPPING 从每个 JQuickRow 读取左侧字段,例如 id,并将右侧"主键"作为对应 Excel 列标题。HEADER=true 使这些标题位于首行。
因此,数据准备的核心是字段契约,而非列号。Java 中 first.put("name", "Alice") 与 XML 的 "name":"姓名" 对应,导出后"姓名"列写入 Alice。若 Java 写成 first.put("姓名", "Alice") 而 XML 仍然期待 name,本文所依据的公开规则没有说明会自动反向识别该键。应先统一数据键或修改 XML 左侧。
LinkedHashMap 在示例中有助于读者观察构造过程,但列顺序不应依赖它。XML 的 MAPPING 顺序才是对外工作簿结构的声明。这样当业务服务的 Map 来源变化、查询字段排序变化时,只要字段名不变,模板列顺序仍保持由配置控制。
数据准备也与显示格式分离。金额、日期、比例等值应尽量保留业务可用的类型;README 说明 FORMAT 负责最终 Excel 显示,而 TRANSFORM 负责在行上下文中计算转换值。本文的转换链路只负责将字段和值送入导出规则,不应为了展示而预先把所有数据拼接成字符串。
XML 服务代理将业务调用和工作簿规则隔开。调用方不需要直接处理工作表、首行或单元格坐标;它只需准备行和输出流,并调用定义好的服务方法。反过来,XML 改变标题、工作表或格式时,应评审这些变更是否仍与 Java 数据字段相符。
注意事项
第一,使用 README 的公开顺序准备数据:JObjectConverter.convert(students) 后调用 JQuickRow.toRows(...)。不要把未经转换的 Map 列表直接传给 JQuickExcelExportXmlParseFactory,因为本文仅验证了 README 展示的 List<JQuickRow> 构造链路。
第二,Map 键必须对齐导出 MAPPING 左侧。XML 写 "id":"主键" 时,Java 应提供 id;XML 写 "enrollmentDate":"入学时间" 时,Java 应提供 enrollmentDate。右侧中文标题不应被当作数据键。
第三,列顺序应由 XML MAPPING 管理。即使示例选择 LinkedHashMap,也不要将 Java Map 的插入顺序当成工作簿结构契约。新增、调整或删除列时,应先修改 XML 并同步确认数据键。
第四,输出流需在服务方法结束前保持打开。示例的 try-with-resources 范围包含创建解析器、创建代理和调用 exportStudents。如果流提前关闭,代理没有可用的写入目标。
第五,XML 资源与接口绑定必须准确。jquick-excel.xml 放入类路径,namespace 使用 com.example.RowExcelService,excel name 使用 exportStudents,接口也应有同名方法。不要把数据准备问题和代理绑定问题混在一起排查。
第六,字段值转换与字段准备分开。测试 XML 中可见 TRANSFORM 对性别、年龄、入学时间的表达式;README 中也说明 FORMAT 用于显示格式。基础 Map 只需按业务字段提供值,具体转换应在已验证的 XML 规则范围内处理。
第七,验收不仅检查是否产生文件。打开 students.xlsx,确认"学生表"首行是"主键、姓名、年龄",后续两行分别含 1001/Alice/20 和 1002/Bob/21。若某列标题存在但数据为空,优先检查同名 Map 键是否遗漏或拼写不同。
总结
导出数据可沿公开链路由 List<Map<String, Object>> 经过 JObjectConverter.convert 与 JQuickRow.toRows 准备为解析器需要的行集合。Java 侧负责提供业务字段和值,XML 则负责列标题、顺序和工作表规则,二者以映射左侧字段名建立稳定连接。
定位问题时,应分别核对数据键、XML 方法绑定和输出流生命周期。文件生成并不代表每列已正确取值,仍需检查标题与实际数据行;当字段为空或列错位时,优先从 Map 键与 MAPPING 的一致性开始排查。