<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency>
一,分批導入
1.首先配置表格頭映射類
@Getter
@Setter
@EqualsAndHashCode
public class IndexOrNameData {/*** 強制讀取第三個 這里不建議 index 和 name 同時用,要么一個對象只用index,要么一個對象只用name去匹配*/@ExcelProperty(index = 2)private Double doubleData;/*** 用名字去匹配,這里需要注意,如果名字重復,會導致只有一個字段讀取到數據*/@ExcelProperty("字符串標題")private String string;@ExcelProperty("日期標題")private Date date;
}
2.編寫excel數據讀監聽器
// 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然后里面用到spring可以構造方法傳進去
@Slf4j
public class DemoDataListener implements ReadListener<IndexOrNameData > {/*** 每隔5條存儲數據庫,實際使用中可以100條,然后清理list ,方便內存回收*/private static final int BATCH_COUNT = 100;/*** 緩存的數據*/private List<IndexOrNameData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);/*** 假設這個是一個DAO,當然有業務邏輯這個也可以是一個service。當然如果不用存儲這個對象沒用。*/private DemoDAO demoDAO;public DemoDataListener() {// 這里是demo,所以隨便new一個。實際使用如果到了spring,請使用下面的有參構造函數demoDAO = new DemoDAO();}/*** 如果使用了spring,請使用這個構造方法。每次創建Listener的時候需要把spring管理的類傳進來** @param demoDAO*/public DemoDataListener(DemoDAO demoDAO) {this.demoDAO = demoDAO;}/*** 這個每一條數據解析都會來調用** @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/@Overridepublic void invoke(IndexOrNameData data, AnalysisContext context) {log.info("解析到一條數據:{}", JSON.toJSONString(data));cachedDataList.add(data);// 達到BATCH_COUNT了,需要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOMif (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存儲完成清理 listcachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}}/*** 所有數據解析完成了 都會來調用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 這里也要保存數據,確保最后遺留的數據也存儲到數據庫saveData();log.info("所有數據解析完成!");}/*** 加上存儲數據庫*/private void saveData() {log.info("{}條數據,開始存儲數據庫!", cachedDataList.size());demoDAO.save(cachedDataList);log.info("存儲數據庫成功!");}
}
分批插入的實現是在invoke方法中,當讀取緩存數達到我們預期的插入數量時就進行插入,然后重新更新list,原本的list就會被回收,達到方式內存溢出的效果,可以在這個方法中進行行參數校驗,有異常拋出即可
3.編寫讀入方法
// 寫法1:fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 這里 需要指定讀用哪個class去讀,然后讀取第一個sheet 文件流會自動關閉EasyExcel.read(fileName, IndexOrNameData.class, new DemoDataListener()).sheet().doRead();// 寫法2fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 一個文件一個readertry (ExcelReader excelReader = EasyExcel.read(fileName, IndexOrNameData.class, new DemoDataListener()).build()) {// 構建一個sheet 這里可以指定名字或者noReadSheet readSheet = EasyExcel.readSheet(0).build();// 讀取一個sheetexcelReader.read(readSheet);}
二,動態表頭分批導出
1.構建表頭和數據
//獲取表頭
private static List<String> makeHeads() {List<String> heads = new ArrayList<>(); //表頭信息heads.add("唯一標識");heads.add("名稱");heads.add("類型");return heads;}//獲取數據private static List<Map<String, Object>> makeData() {List<Map<String, Object>> list = new ArrayList<>();//Map<String,Object> test1 = new LinkedHashMap<>(); //手動添加測試數據(可根據需要從數據庫查詢)test1.put("id", 1);test1.put("name", 2);test1.put("str", 3);list.add(test1);//Map<String,Object> test2 = new LinkedHashMap<>();test2.put("id", 11);test2.put("name", 22);test2.put("str", 33);list.add(test2);return list;}
2.寫出代碼
public void exportExcel(HttpServletResponse httpServletResponse,@RequestParam(required = false) String fileName,@RequestParam(required = false) List<String> heads, @RequestParam(required = false) List<Map<String, Object>> list) throws IOException {if (StringUtils.isEmpty(fileName)){ //文件名稱也可以動態獲取fileName = System.currentTimeMillis() + ".xlsx";} else {fileName = fileName + ".xlsx";}if(heads == null || heads.size() == 0){heads = makeHeads();}if(list == null || list.size() == 0){list = makeData();}OutputStream os= responseInfo(httpServletResponse, fileName); // 調用responseInfo方法List<List<String>> hs = new ArrayList<>();for (String s : heads) {hs.add(Arrays.asList(s));}List<List<Object>> list2 = new ArrayList<>();for (int i = 0; i < list.size(); i++) {List<Object> objects = new ArrayList<>();Collection<Object> values = list.get(i).values();for (Object value : values) {objects.add(value.toString());}list2.add(objects);}// 頭的策略WriteCellStyle headWriteCellStyle = new WriteCellStyle();// 背景設置為紅色headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short)20);headWriteCellStyle.setWriteFont(headWriteFont);// 內容的策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();// 這里需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUND 不然無法顯示背景顏色.頭默認了 FillPatternType所以可以不指定contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 背景綠色contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());WriteFont contentWriteFont = new WriteFont();// 字體大小contentWriteFont.setFontHeightInPoints((short)20);contentWriteCellStyle.setWriteFont(contentWriteFont);// 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現HorizontalCellStyleStrategy horizontalCellStyleStrategy =new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);//創建一個行高設置處理器,我這里直接用匿名內部類類了AbstractRowHeightStyleStrategy abstractRowHeightStyleStrategy = new AbstractRowHeightStyleStrategy() {@Overrideprotected void setHeadColumnHeight(Row row, int relativeRowIndex) {if (relativeRowIndex==0){row.setHeightInPoints(50);}else {row.setHeightInPoints(10);}}@Overrideprotected void setContentColumnHeight(Row row, int relativeRowIndex) {//默認主體的高度row.setHeightInPoints(10);}};//創建列寬設置處理器AbstractHeadColumnWidthStyleStrategy abstractHeadColumnWidthStyleStrategy = new AbstractHeadColumnWidthStyleStrategy() {@Overrideprotected Integer columnWidth(Head head, Integer columnIndex) {switch (columnIndex) {case 0:return 6;case 1:return 20;case 2:return 20;default:return 13;}}};WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();//開始寫出ExcelWriter build = EasyExcel.write(os).head(hs)//注冊內容以及表頭處理器.registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle ,contentWriteCellStyle))//注冊行高處理器.registerWriteHandler(abstractRowHeightStyleStrategy)//注冊列寬處理器.registerWriteHandler(abstractHeadColumnWidthStyleStrategy).build();//然后就可以用上邊的buid對象往指定的sheet中寫入數據了,當數據量大的時候,我們就可以分批寫入,偽代碼如下for(a a:list){build.write(list2,writeSheet);}}/*** 功能:公用方法,寫回瀏覽器* [response, fileName]* @return {@link OutputStream}* @throws*/public static OutputStream responseInfo(HttpServletResponse response, String fileName) throws IOException {// 這里注意有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postmanresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment; filename*=utf-8''" + fileName);OutputStream os=response.getOutputStream();return os;}/*** 如果要兼容swagger用這個,上面的注釋掉* 功能:公用方法* 參數:fileName 文件名稱, 如:123.xlsxpublic static OutputStream responseInfo(HttpServletResponse response, String fileName) throws IOException {response.setCharacterEncoding("utf-8");response.setContentType("APPLICATION/OCTET-STREAM");response.addHeader("Content-Disposition", "attachment;filename=" + fileName);OutputStream os=response.getOutputStream();return os;}*/