告别硬编码,让你的POI导入导出拥抱变化
GitHub地址 | 博客 | 中文 | English | 原文链接
为什么使用AutoExcel?
Excel导入导出在软件开发中非常常见,只要你接触过开发,就一定会遇到。相信很多人会跟我一样选择用Apache POI来完成这项工作,在感受到POI功能强大的同时,我的团队也遇到了以下问题:
- 直接使用POI操作Excel将产生大量硬编码,你会在编码中写死行索引和列索引
- 大量不可复用的格式控制编码,如背景色、对齐方式、单元格样式等
- 实施顾问明明提供了现成的模板,却还要开发用代码实现一遍,开发效率低下
- 模板调整时不得不动用开发资源
- 简单的导出也需要写特定的代码
AutoExcel解决了上述问题,它非常简单,只需要少量的代码即可完成复杂的导入导出;使用它时,程序员对导入导出无感,即不需要直接操作POI;与此同时,实施顾问提供的Excel即是导入导出模板,除非新增数据源或字段,否则模板更新不需要动用开发资源。
AutoExcel并没有对POI进行过重的封装,而是充分利用了Excel本身具有的特性——名称管理器,通过一些小技巧,将单元格与数据源产生映射,从而解耦程序员与POI,避免产生硬编码,让导入导出工作变得愉快而不再是枯燥乏味。
功能预览
| 导出前 | 导出后 |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
实现以上导出只需要编写以下少量代码(你需要额外的代码来准备数据源,例如从数据库中获取)
List<TemplateExportPara> paras = new ArrayList<>();
paras.add(new TemplateExportPara("BusinessUnit", DataGenerator.genBusinessUnit()));
paras.add(new TemplateExportPara("Contract", DataGenerator.genContracts()));
paras.add(new TemplateExportPara("Project", DataGenerator.genProjects()));
List<Product> products = DataGenerator.genProducts();
TemplateExportPara para3 = new TemplateExportPara("Product", products);
para3.setInserted(true);
paras.add(para3);
TemplateExportPara para5 = new TemplateExportPara("Product2", products);
para5.setDataDirection(DataDirection.Right);
paras.add(para5);
ExcelSetting excelSetting = new ExcelSetting();
excelSetting.setRemovedSheets(Arrays.asList("will be removed"));
AutoExcel.save(this.getClass().getResource("/template/Common.xlsx").getPath(),
this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx",
paras,
excelSetting);
认识模板
要实现以上导出,首先需要完成模板的制作。一些报表制作工具如微软的RDL,你会在RDL中制作好导出模型,然后结合代码将数据导出到Excel。这个过程,RDL仅仅起到中介作用,意味着每次有新的导出任务,都得先制作一个导出模型。在AutoExcel中,Excel即模板,如果你的Excel来源是实施顾问,很可能这个Excel已经设定好数据格式、单元格样式等,就等着你往上填数据,既然如此,何不就将这份Excel作为我们的导出模板,我们要做的,仅仅是在其中加入我们的东西而已。
名称管理器
Excel中的名称管理器,这个被大多数人忽视的功能,却成为AutoExcel中连接数据源与单元格的桥梁。你可以通过点击菜单公式->名称管理器来打开名称管理器,其中每一个名称都对应Excel中的某个具体位置,可以是一个区域,也可以是一个单元格,当然,在这里,我们定义的名称都指向单元格。因此可以这么理解,名称管理器就是用来给单元格命名的。正是因为单元格有了名字,我们才能实现给单元格自动赋值而无需个性化的代码。

为单元格定义了名称之后,当你再次点击该单元格,会发现左上角的位置显示了你刚才定义的名称

除了在名称管理器中新增名称,还有一种方式更加直观快捷。点击你想要命名的单元格,然后直接在左上角输入名称,最后按Entry键即可。推荐使用这种方式创建名称。

名称规则
由于单元格名称决定了何种数据按什么方式进行填充,因此必须按以下规则进行命名:
- 数据源名称.字段名称[.合计类型],用于填充普通字段或普通字段的合计值,如:product.SaleArea.sum
- 数据源名称.Formula.xxxx,用于填充公式,如:product.Formula.1
- 数据源名称.RowNo,用于填充行号,如:product.RowNo
所有名称均不区分大小写,以下会根据具体场景分别进行介绍
导出
基础对象

如图所示,批注中注明了每个单元格的名称,按照数据源名称.字段名称的规则书写
Java代码:
String templatePath = this.getClass().getResource("/template/Common.xlsx").getPath();
String outputPath = this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx";
//DataGenerator.genBusinessUnit()用于生成demo数据
TemplateExportPara para = new TemplateExportPara("BusinessUnit", DataGenerator.genBusinessUnit());
AutoExcel.save(templatePath, outputPath, para);
单表

如果你想导出的是一个列表数据,你只需要按照基础对象的书写规则进行命名即可。当然,列表数据导出往往比基础对象复杂,比如,你可能需要一列行号,而又不想在代码中做特殊处理,这时候你可以使用数据源名称.RowNo,将工作交给AutoExcel来处理。注意,RowNo是内置字段,如果数据源中包含此字段,将会被覆盖。
还有一种情况非常常见,你有一个带公式的单元格在表格中,如:=E6+F6,你希望下一行的单元格被赋值=E7+F7,这时你应该使用数据源名称.Formula.xxxx,你可以使用任何你喜欢的公式,AutoExcel最终都会帮你自动填充。xxxx的部分你可以随便书写,只要保证名称唯一即可。Formula同样是内置字段。
Java代码:
String templatePath = this.getClass().getResource("/template/Common.xlsx").getPath();
String outputPath = this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx";
//DataGenerator.genContracts()用于生成demo数据
TemplateExportPara para = new TemplateExportPara("Contract", DataGenerator.genContracts());
AutoExcel.save(templatePath, outputPath, para);
多表

在一个Sheet中导出多个表格,如果你有这样的需求,请在代码中将不是处于最下面的表格的导出参数设置为:setInserted(true),如上图,products对应的导出参数para应做如下设置:para.setInserted(true)。要知道,AutoExcel是不理会数据导出是否有足够空间的,它只会一个劲地输出,所以当你的模板空间不够时,你需要告诉AutoExcel,之后AutoExcel会在导出前腾出足够的空间来容纳你的数据。
这里引入了新的命名规则:数据源名称.字段名称.合计类型,用于对指定字段进行合计,目前支持两种合计类型:Sum和Avg。
Java代码:
String templatePath = this.getClass().getResource("/template/Common.xlsx").getPath();
String outputPath = this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx";
List<TemplateExportPara> paras = new ArrayList<>();
//DataGenerator.genProjects()用于生成demo数据
paras.add(new TemplateExportPara("Project", DataGenerator.genProjects()));
//DataGenerator.genProducts()用于生成demo数据
TemplateExportPara para = new TemplateExportPara("Product", DataGenerator.genProducts());
para.setInserted(true); //导出空间不够时需设置
paras.add(para);
AutoExcel.save(templatePath, outputPath, paras);
向右填充

如果你需要将数据向右而不是向下填充,你只需要使用setDataDirection(DataDirection.Right)即可。
Java代码:
String templatePath = this.getClass().getResource("/template/Common.xlsx").getPath();
String outputPath = this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx";
TemplateExportPara para = new TemplateExportPara("Product2", DataGenerator.genProducts());
para.setDataDirection(DataDirection.Right); //向右填充
AutoExcel.save(templatePath, outputPath, para);
直接导出
直接导出,即导出过程不需要借助模板,适用于集成到后台系统的通用导出功能中,代码非常简单
String outputPath = this.getClass().getResource("/").getPath() + "Export.xlsx";
DirectExportPara para = new DirectExportPara(DataGenerator.genProjects());
AutoExcel.save(outputPath, para);
效果:

当然,你并不会喜欢这样的标题和标题顺序,因此,你需要借助FieldSetting来让你的标题可读且按照你喜欢的顺序来展现。
List<FieldSetting> fieldSettings = new ArrayList<FieldSetting>() {{
add(new FieldSetting("projName", "Project Name"));
add(new FieldSetting("basalArea", "Basal Area"));
add(new FieldSetting("buildingArea", "Building Area"));
add(new FieldSetting("insideArea", "Inside Area"));
add(new FieldSetting("availableArea", "Available Area"));
add(new FieldSetting("availablePrice", "Available Price"));
add(new FieldSetting("availableAmount", "Available Amount"));
}};
String outputPath = this.getClass().getResource("/").getPath() + "Export.xlsx";
DirectExportPara para = new DirectExportPara(DataGenerator.genProjects(), "Projects", fieldSettings);
AutoExcel.save(outputPath, para);
最终效果:

自定义操作
AutoExcel致力于处理导入导出的通用场景,如果有个性化的需求,你应该取回Workbook的控制权,根据你的需求进行个性化处理。save方法提供了两个Consumer,其中actionAhead会在导出操作开始之前被调用,actionBehind会在导出完成之后被调用,你完全可以通过这两个Consumer来添加你想要的功能。
String templatePath = this.getClass().getResource("/template/Common.xlsx").getPath();
String outputPath = this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx";
List<TemplateExportPara> paras = new ArrayList<>();
paras.add(new TemplateExportPara("BusinessUnit", DataGenerator.genBusinessUnit()));
Consumer<Workbook> actionAhead = Workbook -> {
//做任何你想做的事情
};
Consumer<Workbook> actionBehind = workbook -> {
//做任何你想做的事情
};
AutoExcel.save(templatePath, outputPath, paras, actionAhead, actionBehind);
导入
相较于导出,导入具有以下特点:
- 仅支持一种名称规则:
数据源名称.字段名称 - 暂不支持一个Sheet有多个表的情况
- 默认数据读取方向(DataDirection)为
null,即读取基础对象,如需读取列表,需指明读取方向为Down,暂不支持Right方向的读取。
Java代码:
List<ImportPara> importParas = new ArrayList<ImportPara>() {{
add(new ImportPara("BusinessUnit"));
add(new ImportPara("Contract", DataDirection.Down));
add(new ImportPara("Project", DataDirection.Down));
//add(new ImportPara("Product", DataDirection.Down)); 暂不支持
}};
String fileName = this.getClass().getResource("/").getPath() + "ExportWithTemplate.xlsx";
HashMap<String, List<HashMap<String, Object>>> datas = AutoExcel.read(fileName, importParas);
运行示例代码
完整的示例代码请前往单元测试查看

告别硬编码,让你的POI导入导出拥抱变化的更多相关文章
- Java利用POI导入导出Excel中的数据
首先谈一下今天发生的一件开心的事,本着一颗android的心我被分配到了PB组,身在曹营心在汉啊!好吧,今天要记录和分享的是Java利用POI导入导出Excel中的数据.下面POI包的下载地 ...
- poi 导入导出的api说明(大全)
原文链接:http://www.cnblogs.com/qingruihappy/p/8443101.html poi 导入导出的api说明(大全) 一. POI简介 ApachePOI是Apache ...
- 告别硬编码-发个获取未导出函数地址的Dll及源码
还在为找内核未导出函数地址而苦恼嘛? 还在为硬编码通用性差而不爽吗? 还在为暴搜内核老蓝屏而痛苦吗? 请看这里: 最近老要用到内核未导出的函数及一些结构,不想再找特征码了,准备到网上找点符号文件解析的 ...
- 告别硬编码,mysql 如何实现按某字段的不同取值进行统计
上周我突然意识到,我在grafana上写的 sql 语句存在多处硬编码.这篇笔记将记录如何实现没有硬编码的sql语句,以及自学编程过程中如何应对自己的笨拙代码和难题不断的状况. 1.有效但粗笨的硬编码 ...
- POI导入导出excel(附工具类)
关于POI导出excel的功能我在前面的文章已经写过了,POI导出excel的三种方式 , 导出表格数据到excel并下载(HSSFWorkbook版) ,本篇文章主要是将导入导出功能进一步地封装,在 ...
- POI导入导出
一.使用POI导出Execl表格 需要的jar包 package cn.yxj.poi; import java.io.FileOutputStream; import java.util.Date; ...
- Excel基于POI导入导出的Annotation化之路(一)
Excel在web项目里的使用变得越来越广泛,特别是和线下耦合度较高的业务,Excel导入导出变得非常频繁,尽管很多人写了诸多的工具方法,但是终究没有解决一个问题:有效的控制字段英文名称和实际表头名称 ...
- Java POI导入导出Excel
1.异常java.lang.NoClassDefFoundError: org/apache/poi/UnsupportedFileFormatException 解决方法: 使用的poi的相关jar ...
- java中使用poi导入导出excel文件_并自定义日期格式
Apache POI项目的使命是创造和保持java API操纵各种文件格式基于Office Open XML标准(OOXML)和微软的OLE复合文档格式(OLE2)2.总之,你可以读写Excel文件使 ...
随机推荐
- Socket原理及实践(Java/C/C++)
原理 基本概念 什么是TCP/IP.UDP? 详细的可以看一下这个:学习计算机网络知识只要一篇就够了! TCP/IP(Transmission Control Protocol/Internet Pr ...
- 操作系统-文件系统(5)Linux文件系统管理
主流分区机制有MBR和GPT两种 MBR分区:第一个扇区最重要,里面有:引导代码(Bootstrap code area)及分区表(partition table), 其中Bootstrap code ...
- Python目录与文件操作
一.判断一个路径是否存在 os.path.exists(path) 如果路径存在则返回True,否则返回False. import os import getpass # 获取当前系统用户名 user ...
- 关于c++11 memory order的理解
关于C++memory_order的理解 看了c++并发编程实战的内存模型部分后,一直对memory_order不太懂,今天在知乎发现了百度的brpc,恰好有关于原子操作的文档,感觉解释的很好.为了加 ...
- 玩转Spring——Spring事务
事务的概念:多个sql 操作 要么都成功 要么都失败 ACID特性:原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生.一致性(Consisten ...
- WPF新手快速入门系列 1.布局
[概要] 该系列文章主要描述,新手如何快速上手做wpf开发.看过网上部分的教程,主要讲述的是介绍控件.这并没有问题,但是没有把自己的使用经验也完整的描述出来. 所以特此编写此系列文章希望能帮助到,因为 ...
- Python开发的入门教程(六)-函数
介绍 本文主要介绍Python中函数的基本知识和使用 Python之什么是函数 我们知道圆的面积计算公式为: S = πr² 当我们知道半径r的值时,就可以根据公式计算出面积.假设我们需要计算3个不同 ...
- 4300 字Python列表使用总结,用心!
今天列表专题的目录如下: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这 ...
- Android(java)开发之将double类型,强制保留到小数点后两位解决方法。
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 前提:当我们进行double数据类型与double进行运算的时候,可能会遇到 得到小数点后好多位,但是 ...
- Selenium使用cookis登录,并临时将cookis存储在本地【shelve数据库】
Python中自带了一个shelve库,可以帮助我们存储一些少量的数据. shelve数据库类似redis,是以[键值对]的方式进行数据的存储,有点像"字典"这种数据结构,存储在本 ...







