前段时间项目上须要导入一个3000多条数据的Excel耗时十几分钟,使用的是传统的Apache poi的,最后统一决定用阿里的开源easy excel。
阿里在18年3月份左右发布easyexcel,刚发布就是打着“低内存”解决POI的oom的口号,本人在测试过程当中发现相比poi,确实在使用的体验、操做简便性上有很多提高,感谢阿里团队的努力,官方给出的项目demp的github地址:https://github.com/alibaba/easyexceljava
easyexcel核心功能
- 读任意大小的0三、07版Excel不会OOM
- 读Excel自动经过注解,把结果映射为java模型
- 读Excel支持多sheet
- 读Excel时候是否对Excel内容作trim()增长容错
- 写小量数据的03版Excel(不要超过2000行)
- 写任意大07版Excel不会OOM
- 写Excel经过注解将表头自动写入Excel
- 写Excel能够自定义Excel样式 如:字体,加粗,表头颜色,数据内容颜色
- 写Excel到多个不一样sheet
- 写Excel时一个sheet能够写多个Table
- 写Excel时候自定义是否须要写表头
SpringBoot集成
一、pom依赖引入
<!--阿里easyexcel--> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>1.1.2-beta5</version> </dependency>
二、ExcelUtil工具类
package com.example.mybaties.utils; import com.alibaba.excel.EasyExcelFactory; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.metadata.BaseRowModel; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.StringUtils; import com.example.mybaties.entity.UserExcelProperty; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.io.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * @DESCRIPTION 阿里Excel工具类 * @Author lst * @Date 2020-04-28 11:30 */ @Slf4j public class ExcelUtil { private static Sheet initSheet; /** * 默认初始化 */ static { //sheetNo: sheet页码,默认为1 //headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取 initSheet = new Sheet(1, 0); initSheet.setSheetName("sheet"); //设置自适应宽度 initSheet.setAutoWidth(Boolean.TRUE); } /** * @Description: 导入方法,读取少于1000行数据 * @author: lst * @date: 2020-4-28 11:26 * @param filePath 文件绝对路径 * @return List<Object> */ public static List<Object> readLessThan1000Row(String filePath){ return readLessThan1000RowBySheet(filePath,null); } /** * @Description: 导入方法,读小于1000行数据, 带样式 * @author: lst * @date: 2020-4-28 11:27 * @param filePath 文件绝对路径 * @param sheet sheet对象 * sheetNo: sheet页码,默认为1 * headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取 * clazz: 返回数据List<Object> 中Object的类名 * @return List<Object> */ public static List<Object> readLessThan1000RowBySheet(String filePath, Sheet sheet){ if(!StringUtils.hasText(filePath)){ return null; } //判断是否使用默认的sheet sheet = sheet != null ? sheet : initSheet; InputStream fileStream = null; try { fileStream = new FileInputStream(filePath); return EasyExcelFactory.read(fileStream, sheet); } catch (FileNotFoundException e) { log.info("找不到文件或文件路径错误, 文件:{}", filePath); }finally { try { if(fileStream != null){ fileStream.close(); } } catch (IOException e) { log.info("excel文件读取失败, 失败缘由:{}", e); } } return null; } /** * @Description 导入方法,读大于1000行数据 * @author lst * @date 2020-4-28 11:32 * @param filePath 文件绝对路径 * @return List<Object> */ public static List<Object> readMoreThan1000Row(String filePath){ return readMoreThan1000RowBySheet(filePath,null); } /** * @Description 导入方法,读大于1000行数据, 带样式 * @author lst * @date 2020-4-28 11:33 * @param filePath 文件绝对路径 * @param sheet sheet对象 * sheetNo: sheet页码,默认为1 * headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取 * clazz: 返回数据List<Object> 中Object的类名 * @return List<Object> */ public static List<Object> readMoreThan1000RowBySheet(String filePath, Sheet sheet){ if(!StringUtils.hasText(filePath)){ return null; } //判断是否使用默认的sheet sheet = sheet != null ? sheet : initSheet; InputStream fileStream = null; try { fileStream = new FileInputStream(filePath); ExcelListenerPlus excelListener = new ExcelListenerPlus(); EasyExcelFactory.readBySax(fileStream, sheet, excelListener); return excelListener.getDatas(); } catch (FileNotFoundException e) { log.error("找不到文件或文件路径错误, 文件:{}", filePath); }finally { try { if(fileStream != null){ fileStream.close(); } } catch (IOException e) { log.error("excel文件读取失败, 失败缘由:{}", e); } } return null; } /** * @Description 导出方法,生成excle * @author lst * @date 2020-4-28 11:37 * @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx * @param data 数据源 * @param head 表头 */ public static void writeBySimple(String filePath, List<List<Object>> data, List<String> head){ writeSimpleBySheet(filePath,data,head,null); } /** * @Description 导出方法,生成excle * @author lst * @date 2020-4-28 11:37 * @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx * @param data 数据源 * @param sheet excle页面样式 * @param head 表头 */ public static void writeSimpleBySheet(String filePath, List<List<Object>> data, List<String> head, Sheet sheet){ //判断是否使用默认的sheet sheet = (sheet != null) ? sheet : initSheet; if(head != null){ List<List<String>> list = new ArrayList<>(); head.forEach(h -> list.add(Collections.singletonList(h))); sheet.setHead(list); } OutputStream outputStream = null; ExcelWriter writer = null; try { outputStream = new FileOutputStream(filePath); writer = EasyExcelFactory.getWriter(outputStream); writer.write1(data,sheet); } catch (FileNotFoundException e) { log.error("找不到文件或文件路径错误, 文件:{}", filePath); }finally { try { if(writer != null){ writer.finish(); } if(outputStream != null){ outputStream.close(); } } catch (IOException e) { log.error("excel文件导出失败, 失败缘由:{}", e); } } } /** * @Description 导出方法,生成excle * @author lst * @date 2020-4-28 11:39 * @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx * @param data 数据源 */ public static void writeWithTemplate(String filePath, List<? extends BaseRowModel> data){ writeWithTemplateAndSheet(filePath,data,null); } /** * @Description 导出方法,生成excle * @author lst * @date 2020-4-28 11:40 * @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx * @param data 数据源 * @param sheet excle页面样式 */ public static void writeWithTemplateAndSheet(String filePath, List<? extends BaseRowModel> data, Sheet sheet){ if(CollectionUtils.isEmpty(data)){ return; } //判断是否使用默认的sheet sheet = (sheet != null) ? sheet : initSheet; sheet.setClazz(data.get(0).getClass()); OutputStream outputStream = null; ExcelWriter writer = null; try { outputStream = new FileOutputStream(filePath); writer = EasyExcelFactory.getWriter(outputStream); writer.write(data,sheet); } catch (FileNotFoundException e) { log.error("找不到文件或文件路径错误, 文件:{}", filePath); }finally { try { if(writer != null){ writer.finish(); } if(outputStream != null){ outputStream.close(); } } catch (IOException e) { log.error("excel文件导出失败, 失败缘由:{}", e); } } } /** * @Description 导出方法,生成多Sheet的excle * @author lst * @date 2020-4-28 11:41 * @param filePath 绝对路径, 如:/home/chenmingjian/Downloads/aaa.xlsx * @param multipleSheelPropetys * @return */ public static void writeWithMultipleSheel(String filePath,List<MultipleSheelPropety> multipleSheelPropetys){ if(CollectionUtils.isEmpty(multipleSheelPropetys)){ return; } OutputStream outputStream = null; ExcelWriter writer = null; try { outputStream = new FileOutputStream(filePath); writer = EasyExcelFactory.getWriter(outputStream); for (MultipleSheelPropety multipleSheelPropety : multipleSheelPropetys) { Sheet sheet = multipleSheelPropety.getSheet() != null ? multipleSheelPropety.getSheet() : initSheet; if(!CollectionUtils.isEmpty(multipleSheelPropety.getData())){ sheet.setClazz(multipleSheelPropety.getData().get(0).getClass()); } writer.write(multipleSheelPropety.getData(), sheet); } } catch (FileNotFoundException e) { log.error("找不到文件或文件路径错误, 文件:{}", filePath); }finally { try { if(writer != null){ writer.finish(); } if(outputStream != null){ outputStream.close(); } } catch (IOException e) { log.error("excel文件导出失败, 失败缘由:{}", e); } } } /*********************匿名内部类开始,能够提取出去******************************/ @Data public static class MultipleSheelPropety{ private List<? extends BaseRowModel> data; private Sheet sheet; } /** * 解析监听器,读取大于一千行的数据须要传入次参数 * 每解析一行会回调invoke()方法。 * 整个excel解析结束会执行doAfterAllAnalysed()方法 * @author lst * @date 2020-4-28 11:42 */ @Data public static class ExcelListenerPlus extends AnalysisEventListener { private List<Object> datas = new ArrayList<>(); /** * @Description 逐行解析 * @author lst * @date 2020-4-28 11:43 * @param object 当前行的数据 * @param context */ @Override public void invoke(Object object, AnalysisContext context) { //当前行 // context.getCurrentRowNum() if (object != null) { datas.add(object); } } /** * @Description 解析完全部数据后会调用该方法 * @author lst * @date 2020-4-28 11:43 * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { //解析结束销毁不用的资源 } } public static class ExcelListener extends AnalysisEventListener<UserExcelProperty> { private List<UserExcelProperty> datas = new ArrayList<>(); private static final int BATCH_COUNT = 1; @Override public void invoke(UserExcelProperty userExcelProperty, AnalysisContext analysisContext) { //数据存储到datas,供批量处理,或后续本身业务逻辑处理。 datas.add(userExcelProperty); //达到BATCH_COUNT了,须要去存储一次数据库,防止数据几万条数据在内存,容易OOM if(datas.size() >= BATCH_COUNT){ data(); // 存储完成清理datas datas.clear(); } } private void data() { for(UserExcelProperty userExcelProperty : datas){ log.info("姓名:{},性别:{},年龄:{}",userExcelProperty.getName(),userExcelProperty.getSex(),userExcelProperty.getAge()); } } /** * 全部数据解析完成了 都会来调用 */ @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } } }
三、UserExcelProperty实体类
package com.example.mybaties.entity; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.metadata.BaseRowModel; import lombok.Data; /** * @DESCRIPTION UserExcelProperty实体类 * @Author lst * @Date 2020-04-28 10:30 */ @Data public class UserExcelProperty extends BaseRowModel { @ExcelProperty(value = "姓名",index = 0) private String name; @ExcelProperty(value = "性别",index = 1) private String sex; @ExcelProperty(value = "年龄",index = 2) private Integer age; }
四、UserController控制层(测试导入和导出的接口)
package com.example.mybaties.controller; import com.example.mybaties.service.UserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; /** * @author LST * @version 1.0 * @Description: 用户管理 * @date 2019-12-28 16:22 */ @RestController @RequestMapping("/user") @Api(value = "UserController", tags = "用户接口") public class UserController { @Autowired private UserService userService; /** * @Description: excel导出 * @author: lst * @date: 2020-4-28 11:00 */ @GetMapping(value = "/user-export", produces = "application/json; charset=utf-8") public void userExport() { userService.userExport(); } /** * @Description 将用户信息导出下载 * @author lst * @date 2020-4-28 14:54 * @param response */ @GetMapping(value = "/download-export", produces = "application/json; charset=utf-8") public void downLoadExcel(HttpServletResponse response){ userService.downLoadExcel(response); } /** * @Description Excel文件用户信息导入 * @author lst * @date 2020-4-28 15:18 * @param file 文件流 */ @PostMapping(value = "/user-import", produces = "application/json; charset=utf-8") @ApiOperation(value = "Excel文件用户信息导入", notes = "Excel文件用户信息导入", produces = "application/json") public void userImport(@RequestParam(value = "file") MultipartFile file) { userService.userImport(file); } }
五、UserService接口和UserServiceImpl具体实现代码
package com.example.mybaties.service; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; /** * @author lst * @version 1.0 * @Description: 用户表 * @date 2019年04月09日 18:23 */ public interface UserService { void userExport(); void downLoadExcel(HttpServletResponse response); void userImport(MultipartFile file); }
package com.example.mybaties.service.impl; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.support.ExcelTypeEnum; import com.example.mybaties.entity.UserExcelProperty; import com.example.mybaties.service.UserService; import com.example.mybaties.utils.ExcelUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author lst * @version 1.0 * @Description: 用户表实现层 * @date 2019年04月09日 18:23 */ @Service @Slf4j public class UserServiceImpl implements UserService { /** * @Description: excel导出 * @author: lst * @date: 2020-4-28 11:00 */ @Override public void userExport() { Long startTime = System.currentTimeMillis(); //sheetNo: sheet页码,默认为1 //headLineMun: 从第几行开始读取数据,默认为0, 表示从第一行开始读取 Sheet sheet = new Sheet(1,0, UserExcelProperty.class,"用户信息",null); sheet.setAutoWidth(Boolean.TRUE); //设置列宽 设置每列的宽度 /*Map columnWidth = new HashMap(); columnWidth.put(0,10000); columnWidth.put(1,40000);columnWidth.put(2,10000);columnWidth.put(3,10000); sheet1.setColumnWidthMap(columnWidth); sheet1.setHead(createTestListStringHead2());*/ List<UserExcelProperty> data = new ArrayList<>(); for(int i = 0; i < 2000; i++){ UserExcelProperty userExcelProperty = new UserExcelProperty(); userExcelProperty.setAge(i); userExcelProperty.setName("测试"+i); userExcelProperty.setSex("1"); data.add(userExcelProperty); } ExcelUtil.writeWithTemplateAndSheet("D:\\test.xls",data,sheet); Long endTime = System.currentTimeMillis(); log.info("消耗时间:{}ms",endTime-startTime); } /** * @Description 将用户信息导出下载 * @author lst * @date 2020-4-28 14:54 * @param response */ @Override public void downLoadExcel(HttpServletResponse response) { Long startTime = System.currentTimeMillis(); ExcelWriter writer = null; // 文件输出位置 OutputStream out = null; try { out = response.getOutputStream(); writer = new ExcelWriter(response.getOutputStream(), ExcelTypeEnum.XLSX, true); Sheet sheet = new Sheet(1,0, UserExcelProperty.class,"用户信息",null); sheet.setAutoWidth(Boolean.TRUE); //获得要填充的数据 List<UserExcelProperty> data = new ArrayList(); for(int i = 0; i < 2000; i++){ UserExcelProperty userExcelProperty = new UserExcelProperty(); userExcelProperty.setAge(i); userExcelProperty.setName("测试"+i); userExcelProperty.setSex("1"); data.add(userExcelProperty); } writer.write(data, sheet); response.setCharacterEncoding("utf-8"); response.setContentType("application/vnd.ms-excel;charset=utf-8"); response.setHeader("Content-Disposition", "attachment;filename=" + new String(("用户信息.xls").getBytes(), "ISO8859-1")); out.flush(); } catch (IOException e) { log.info("错误信息:{}",e.getMessage()); }finally { writer.finish(); try { out.close(); } catch (IOException e) { e.printStackTrace(); } } Long endTime = System.currentTimeMillis(); log.info("消耗时间:{}ms",endTime-startTime); } /** * @Description Excel文件用户信息导入 * @author lst * @date 2020-4-28 15:18 * @param file 文件流 */ @Override public void userImport(MultipartFile file) { Long startTime = System.currentTimeMillis(); if(!file.isEmpty()){ try { InputStream inputStream = new BufferedInputStream(file.getInputStream()); //实例化实现了AnalysisEventListener接口的类 ExcelUtil.ExcelListener excelListener = new ExcelUtil.ExcelListener(); ExcelReader reader = new ExcelReader(inputStream,null,excelListener); //读取信息 reader.read(new Sheet(1,1,UserExcelProperty.class)); } catch (IOException e) { log.info("错误信息:{}",e.getMessage()); } } Long endTime = System.currentTimeMillis(); log.info("消耗时间:{}ms",endTime-startTime); } }
六、生成Excel数据接口测试(http://127.0.0.1:8001/user/user-export)
使用Swagger测试以下图:git
查看生成的Excel文件信息以下github
七、导出Excel数据下载接口测试(http://127.0.0.1:8001/user/download-export)
使用Swagger测试以下图:web
Excel文件数据就是和上个接口同样。spring
八、导入Excel数据接口测试(http://127.0.0.1:8001/user/user-import)
此接口我只作了打印,如须要插入数据能够在ExcelListener类中去实现数据库
使用Swagger测试以下图:json
后台打印了读取Excel的数据,如图app
动态表头的导入和导出以项目实际需求进行开发。ide