一、EasyExcel简介
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便
二、使用方法
1.引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.8</version>
</dependency>
2.读取excel
(1)默认方式读取
通过EasyExcel.read()方式进行,参数依次为文件路径、要读取成指定文件的类、读取文件类的监听器,sheet中默认读取第一个sheet,也可以自己制定,读取文件时从第二行开始,可以按批次读取,也可以自定义读取
@Test
public void easyExcelRead(){
String path = "D:\\city_district.xlsx";
EasyExcel.read(path, TeleCity.class, new TeleCityListener(teleCityService)).sheet("city市区表").doRead();
}
监听器TeleCityListener,这里是继承了AnalysisEventListener,需要指定读取的类,因为没有被spring管理,所以这里需要new TeleCityListener(teleCityService),传入的teleCityService需要注入,方式来创建实例,通过构造器方式传入注入的dao或者service来赋值,调用相应的方法,插入数据库,也可以使用默认构造器,不会对数据库进行操作
@Slf4j
public class TeleCityListener extends AnalysisEventListener<TeleCity> {
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private TeleCityService teleCityService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param teleCityService
*/
public TeleCityListener(TeleCityService teleCityService) {
this.teleCityService = teleCityService;
}
public TeleCityListener(){}
/**
* 每隔100条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
List<TeleCity> list = new ArrayList<TeleCity>();
/**
* 这个每一条数据解析都会来调用
*
* @param teleCity
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param analysisContext
*/
@Override
public void invoke(TeleCity teleCity, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(teleCity));
list.add(teleCity);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", list.size());
teleCityService.insertTeleCityList(list);
log.info("存储数据库成功!");
}
}
(2)使用自定义的通用监听器读取文件
通过泛型和继承AnalysisEventListener来自定义通用的一个读取监听器,只读取数据,读到的数据全都封装在list里面,通过getList()即可得到返回的数据,想要处理哪种格式就传入相应的类即可
@Slf4j
public class GeneralListener<T> extends AnalysisEventListener<T> {
private List<T> list= Lists.newArrayList();
@Override
public void invoke(T data, AnalysisContext context) {
Assert.notNull(data,"导入数据不能为null");
log.info("start read list of data :{}",JSON.toJSONString(data));
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("read data complete!");
}
public List<T> getList() {
return list;
}
}
创建一个实例,读取完毕后,调用getlist()方法
@Test
public void generalExcelRead(){
String path = "D:\\city_district.xlsx";
//建立一个通用读取监听器,读取数据到这个里面,通过getList方式获取读取的数据
GeneralListener<TeleCity> teleListener=new GeneralListener<>();
//EasyExcel进行读取
try{
EasyExcel.read(path,TeleCity.class,teleListener).sheet("city市区表").doRead();
}catch (Exception e){
log.error("读取数据失败!",e);
}
log.info("读取到的数据大小为:{}", JSON.toJSONString(teleListener.getList().size()));
//进行数据库插入操作
teleCityService.insertTeleCityList(teleListener.getList());
}
3.导出文件为Excel
使用EasyExcel.write()即可,里面参数为文件路径,要转出的文件内容,和sheet名称以及要导出的数据
@Test
public void generateExcelWrite(){
List<TeleCity> list=teleCityService.getList();
List<TeleCityExcelDTO> exportList=JSON.parseArray(JSON.toJSONString(list),TeleCityExcelDTO.class);
log.info("开始写入数据!");
String path = "D:\\demo.xlsx";
try {
EasyExcel.write(path, TeleCityExcelDTO.class).sheet("模板").doWrite(exportList);
}catch (Exception e){
log.error("写入数据失败!",e);
}
log.error("写入数据成功!大小为{}", JSON.toJSONString(list.size()));
}
定义导出的数据文件格式:通过注释来指定哪些列不需要导出,导出列位置和名称以及相应的高度、宽度等
@Data
public class TeleCityExcelDTO implements Serializable {
/**
* 忽略不读取和写入
*/
@ExcelIgnore
private static final long serialVersionUID = 8372588291576645501L;
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 0,value = "id")
@ColumnWidth(10)
private Long id;
@ExcelProperty(index = 1,value = "城市名称")
@ColumnWidth(20)
private String cityName;
@ExcelProperty(index = 2,value = "组织编码")
@ColumnWidth(20)
private String orgCode;
@ExcelProperty(index = 3,value = "物流编码")
@ColumnWidth(20)
private String logisticCode;
@ExcelProperty(index = 4,value = "省份Id")
@ColumnWidth(10)
private Long provinceId;
@ExcelProperty(index = 5,value = "创建时间")
@ColumnWidth(20)
private String createTime;
@ExcelProperty(index = 6,value = "更新时间")
@ColumnWidth(20)
private String updateTime;
}
4.Download下载文件
类似于上面导出文件,需要自定义导出文件内容格式。实际是将导出的文件写入response中,需要指定编码格式和输出流格式等。这里特别注意是需要使用Get方式请求的,将下载的数据在当前界面作为附件方式下载,不会打开新的界面
@GetMapping("/test17")
public void testExportExcel(HttpServletResponse response) {
//导出数据
List<TeleCity> list = teleCityService.getList();
List<TeleCityExcelDTO> exportList = JSON.parseArray(JSON.toJSONString(list), TeleCityExcelDTO.class);
// 设置为导出件格式为excel
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String filePrefix="测试文件";
try {
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(filePrefix, "UTF-8").replaceAll("\\+", "%20");
//Content-disposition 的 attachment参数将文件作为附件下载
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), TeleCityExcelDTO.class).sheet("模板").doWrite(exportList);
} catch (Exception e) {
log.error("下载文件失败!", e);
throw new BizException(ResponseCode.EXPORT_FILE_FAILURE);
}
log.info("下载数据大小为:{}", exportList.size());
}
5.Upload上传文件
类似于前面的读取excel文件,这里是通过前端选取文件,读入到输入流里面,然后通过监听器读取指定格式。注意接收文件的类型为:MultipartFile
@RequestMapping("/test18")
public Response testUpLoadExcel(MultipartFile file){
log.info("文件名称:{}",file.getName());
//构建上传文件的数据格式
GeneralListener<TeleCityExcelDTO> generalListener=new GeneralListener<>();
try {
EasyExcel.read(file.getInputStream(), TeleCityExcelDTO.class, generalListener).sheet().doRead();
} catch (IOException e) {
log.error("上传文件失败!", e);
throw new BizException(ResponseCode.UP_LOAD_FILE_FAILURE);
}
return new Response("0","读取数据成功!",generalListener.getList().size());
}
前端样式,注意读取文件的类型为:multipart/form-data,前端
<form action="/test/test18", method="post", enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit">
</form>