JQuick-Excel 导入反向映射实战:让中文表头稳定落到业务字段
tags: #JQuickExcel #JavaExcel #Excel导入 #DSL #字段映射
简介
导入 Excel 时,用户看到的是"学号""姓名""性别"这样的表头,业务代码需要的却是 no、name、sex 等字段。本文说明 IMPORT WITH 中 MAPPING 的反向映射规则,并给出可独立验证的 XML 与 Java 调用示例。
前言
很多导入实现从列号开始:第一列放学号,第二列放姓名。模板一旦调整列顺序,隐藏在循环里的下标关系就很难核对。JQuick-Excel 的导入规则以表头文本为入口,把模板契约直接写在 XML 中。
导出时,MAPPING 表示"字段到表头";导入时,含义正好相反,表示"表头到字段"。这不是书写习惯差异,而是由数据流方向决定的。先弄清这一点,字段为空、列取错值等问题会少很多。
环境与依赖
项目运行于 Java 8 或更高版本,使用 jquick-excel:3.6.0。将 XML 服务定义放在 src/main/resources/jquick-excel.xml,由 JQuickXmlFactory 按资源名加载。
xml
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-excel</artifactId>
<version>3.6.0</version>
</dependency>
代码示例
下面的模板要求工作表为 Sheet1,第一行依次包含"学号、姓名、性别、年龄、出生日期"。MAPPING 左侧是工作簿里真实出现的标题,右侧是返回 JQuickRow 使用的字段键。
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.github.paohaijiao.xml.service.JQuickExcelExportService">
<excel name="importExcel" returnClass="java.util.List"><![CDATA[
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
MAPPING={
"学号":"no",
"姓名":"name",
"性别":"sex",
"年龄":"age",
"出生日期":"birthday"
},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
}
]]></excel>
</excels>
Java 侧只负责准备输入流、上下文和代理。示例中的字典方向是显示文本到内部编码,不能直接复用导出时的编码到显示文本字典。
java
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface JQuickExcelExportService {
void exportExcel(@Param("field") String field, @Param("value") String value);
List<JQuickRow> importExcel(@Param("field") String field, @Param("value") String value);
}
java
Map<String, Object> gender = new HashMap<>();
gender.put("男", "1");
gender.put("女", "2");
JContext context = new JContext();
context.put("dict", gender);
try (InputStream input = ReverseMappingExample.class.getClassLoader()
.getResourceAsStream("templates/students.xlsx")) {
JQuickParseHandler parser =
new JQuickExcelImportXmlParseFactory(context, input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
JQuickExcelExportService service =
factory.createApi(JQuickExcelExportService.class);
List<JQuickRow> rows = service.importExcel("field", "value");
}
原理说明
导入解析器先按 SHEET 选择工作表。HEADER=true 表示第一行承担表头语义,随后 MAPPING 根据左侧文本寻找对应列,并把每一行的值写到右侧字段名中。以上例为例,标题"学号"对应的单元格值会进入 no,而不是进入"学号"这个键。
这种写法不依赖列的物理位置。只要"学号"标题仍然存在,用户把它从 A 列移动到 C 列,映射的语义仍然明确。反过来,表头从"出生日期"改为"出生年月"时,应把它视为模板契约变更,更新 XML 并重新验证,而不是期待框架按近义词猜测。
TRANSFORM 在映射后处理当前行字段。trans(${dict},${sex}) 中的 ${sex} 是右侧字段键,${dict} 来自 JContext。表头"性别"只属于 MAPPING 的左侧,不能把表头文本当作转换表达式的字段名。
注意事项
IMPORT WITH的MAPPING左侧是 Excel 表头,右侧是目标字段;不要把导出的{"no":"学号"}原样复制到导入规则。SHEET是工作表名称,不是上传文件名。空格、大小写和全半角差异都应按真实模板核对。HEADER=true时,第一行必须是与映射左侧一致的标题行;封面、说明行或空行会改变这个前提。- 映射负责列归属,不负责业务唯一性、权限、跨行校验或持久化。此类规则应放在应用层。
- 用一行可辨识数据构造最小模板,分别验证"学号进入 no""姓名进入 name",再增加字典和日期转换。
总结
反向映射的核心只有一件事:用户提交的表头是输入边界,业务字段是系统内部边界。把这层关系显式写成 "学号":"no",模板的变化就会成为可以评审和测试的配置改动。
实际维护中,我会把导出 "no":"学号" 与导入 "学号":"no" 成对检查。两份规则方向相反,但共同约束同一份模板,才能让导出的样表和导入入口保持一致。