前段時間項目上須要導入一個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