前言:开发中会经常碰到涉及EXCEL的导入导出,对java程序员而言,EXCEL的相关操作绝大多数的人会采用Apache开源的POI进行开发,POI封装的属于比较底层的操作,功能强大,可扩张性极好,同时难度不大,但因为较为底层,封装的不够,对开发人员来说,即便是最简单的导入导出涉及的代码也比较繁琐,而且在开发中会有很多重复代码,这些重复代码你可以自己封装工具类,也可以网上找一些现成的工具类,从而避免重复造轮子的问题,一开始我也是傻乎乎的自己封装轮子,直到后来遇到EASY-POI和HUTOOL,那种感觉就像是从吃自己烧的黑暗料理突然有一天改吃米其林大师烧的大餐,妙不可言...几行代码就可以轻松搞定EXCEL的常见开发,代码看上去非常优雅,即便是从来没学过POI的小白也可以轻松上手.
1.Easy-Poi简介
Easy-Poi是国产的,由国人开源的代码,是在POI的基础上进行封装的Excel操作工具库,这里援引其官网的介绍:
easypoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员 就可以方便的写出Excel导出,Excel模板导出,Excel
导入,Word模板导出,通过简单的注解和模板 语言(熟悉的表达式语法),完成以前复杂的写法
官网地址: http://easypoi.mydoc.io/(有不懂的地方都可以参考这里,有完整的api文档参考)
2.实战
先从最简单的导出开始,需求是:我需要把一个班所有人的姓名,年龄等个人信息导出到Excel文件中.
第一步:引入依赖
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.2.0</version>
</dependency>
第二步:在实体类上添加注解
注解比较多,但简单的导入导出其实你只需要了解一两个即可,比如@Excel,该注解里面可以指定导出的列名,是否导出,第几列等,日常开发只需要了解我下面贴出的这几个即可,更具体可以可以参考官网,为了篇幅和讲解主要内容,这里就不贴出了.
@Data//这个是lombok插件(推荐)添加此注解相当于是get,set,toString等一次性全部自动生成
@ExcelTarget("studentEntity")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "序号",isImportField = "true",orderNum = "1")
private Integer serialNum;
@Excel(name = "学校",isImportField = "true",orderNum = "2")
private String schoolName;
@Excel(name = "姓名",isImportField = "true",orderNum = "3")
private String studentName;
@Excel(name = "右眼",isImportField = "true",orderNum = "4")
private String odVision;
@Excel(name = "左眼",isImportField = "true",orderNum = "5")
private String osVision;
}
@ExcelTarget("Eyesight")
@Data
public class Eyesight {
@Excel(name = "右眼",isImportField = "true",orderNum = "4")
private String odVision;
@Excel(name = "左眼",isImportField = "true",orderNum = "5")
private String osVision;
}
@Data
@ExcelTarget("StudentDTO")
public class StudentDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "序号",isImportField = "true",orderNum = "1")
private Integer serialNum;
@Excel(name = "学校",isImportField = "true",orderNum = "2")
private String schoolName;
@Excel(name = "姓名",isImportField = "true",orderNum = "3")
private String studentName;
@ExcelEntity(id = "Eyesight",name = "裸眼视力",show = true)
private Eyesight eyesight;
}
第三步:编写测试类
public void writeData() {
//封装数据
List<StudentDTO> studentDTOS = new ArrayList<>();
for (int i = 0; i < 2; i++) {
StudentDTO studentDTO = new StudentDTO();
Eyesight eyesight = new Eyesight();
studentDTO.setSerialNum(i);
studentDTO.setSchoolName("测试小学");
studentDTO.setStudentName("王老" + i);
eyesight.setOdVision("5.3");
eyesight.setOsVision("5.0");
studentDTO.setEyesight(eyesight);
studentDTOS.add(studentDTO);
}
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("学生信息","01班"), StudentDTO.class, studentDTOS);//创建工作簿,其中Params中的两个参数分别指定了标题和sheet名
String path = "F:/tmp/"+EXCEL_NAME;//导出路径,我放在了F盘的tmp文件夹下
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(path);
workbook.write(outputStream);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
outputStream.close();
workbook.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
writeData();
}
上面的代码看上去多,其实很多是在封装数据和捕获异常,导出只要一行代码就搞定了,还是非常简单的...
第四步:运行之
在F盘指定目录下成功找到test.xlsx文件,打开后:
OJBK,搞定...
导入跟导出如出一辙,也是非常简单,我就不演示了,官网上全得很...
3.后话
在实际开发中,需求可没那么简单,产品经理那个糟老头子可坏得很,导入和导出要求都比较多,尤其是导出,要显得很高大上才行...
比如像下面这种样式:
再或者下面这种:
像这些相对比较复杂的表结构,如果通过设置导出参数的方式,那难度也太大了,下面介绍一种比较简单好用的方式,就是使用现成的模板,然后数据的话使用我要填充的数据,我以第一张图的模板为例:
在导出时,会把上表里用{{xxx}}和{{$fe:xxx}}表示的数据全部用真实数据替换掉.
对于模板中非循环的元素,如省市区这些,可以采用{{xxx}}即可,对于类似于List类型的数据,则采用{{$fe:xxx}}这种格式.
$fe遍历应该是使用最广的遍历,用来解决遍历后下面还有数据的处理方式 我们要生成的是这个需要一些list集合和一些单纯的数
据fe的写法 fe标志 冒号 list数据 单个元素数据(默认t,可以不写) 第一个元素 {{$fe: maplist t t.id }}
测试代码如下:
public void download() {
TemplateExportParams templateExportParams = new TemplateExportParams("F:/tmp/导出模板.xls");
Map<String,Object> map = new HashMap<>();
map.put("provinceName","浙江省");
map.put("cityName","杭州市");
map.put("areaName","江干区");
map.put("schoolName","杭州第一中学");
map.put("grade","初一");
map.put("nowDate","2018-12-25");
map.put("mmp","");
List<Map<String,String>> mapList = new ArrayList<>();
for (int i = 0; i <4 ; i++) {
Map<String,String> ml = new HashMap<>();
ml.put("id","1");
ml.put("className","一班");
ml.put("studentName","王老");
ml.put("code","10000011");
ml.put("odVision","4.8");
ml.put("osVision","5.3");
ml.put("odSph","+0.3");
ml.put("odCyl","+0.3");
ml.put("odAxial","0.0");
ml.put("osSph","+0.5");
ml.put("osCyl","+0.3");
ml.put("osAxial","-0.2");
ml.put("odSeriesMirror","5");
ml.put("osSeriesMirror","4");
ml.put("odAmetropia","3");
ml.put("osAmetropia","2");
mapList.add(ml);
}
map.put("maplist",mapList);
Workbook workbook = ExcelExportUtil.exportExcel(templateExportParams,map);
File saveFile = new File("F:/tmp/");
if (!saveFile.exists()){
saveFile.mkdir();
}
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("F:/tmp/学生视力报告.xls");
workbook.write(fileOutputStream);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
download();
}
运行测试之:
OK,完全没问题,这里面其实还有个小bug,作者可能还未发现或未修复,细心的朋友可能会发现,我为啥在模板的最后一行加了句
mmp?,就是这个bug浪费了我两个小时,妈卖批! 当然绝无骂作者之意,仅仅是表达一下当时的心情...
也就是说在List{{$fe:xxx}}数据之后,必须要有一个非集合型数据结尾,哪怕这个数据是空的,也必须要有这么个东西在这里占位,否则就会疯狂报错,而且你根本不知道为什么会报错,在网上也搜不到任何相关资料...
一开始我以为是自己的原因,严格按照作者文档进行操作还是会报错,最后我索性直接下载作者的demo,把他demo里{{$fe:xxx}}后面的{{xxx}}数据都删了,也会报同样的错误,确认是个bug了,避免踩坑在这里浪费过多时间...
4.后后话
如果连easy-poi都觉得困难的小白,可以用另外一款工具,hutool,里面几乎封装了Java程序员开发毕生要用到的轮子,以后可以不用重复造了...
关于Excel的导入导出可以直接调用ExcelUtil类中的静态方法,简单到爆,当然也有局限性就是功能没有easy-poi这么强大,适合简单功能的实现和小白.
5.总结
对于功能而言:POI > EASY-POI >HUTOOL,上手易用度而言:HUTOOL>EASPOI>POI,可以根据实际开发灵活选择,个人推荐使用easy-poi,可以轻松搞定大部分企业的需求,同时代码也够简单优雅,除了日常的导入导出,涉及图片,大数据量等要求的都可以轻松搞定...