java 分頁匯出百萬級資料到excel,分頁實現
最近修改了一個匯出員工培訓課程的歷史記錄(一年資料),匯出功能本來就有的,不過前臺做了時間限制(只能選擇一個月時間內的),還有一些必選條件, 匯出的資料非常有侷限性。心想:為什麼要做出這麼多條件限制呢?條件限制無所謂了,能限制匯出資料的準確性,但是時間? 如果我想匯出一年的資料,還要一月一月的去匯出,這也太扯了。於是我試著放開時間js限制,讓使用者自己隨便選好了,然後自己選了一段時間,選了幾門課程,點選按鈕匯出,MD報錯了,看後臺日誌說什麼IO流報異常,看了下程式碼,程式碼也很簡單,查詢資料,用HSSFWorkbook 寫入資料,關閉流,匯出,似乎沒什麼問題。於是去把查詢的sql拉出來,放入資料庫,查詢資料,20w條資料,好吧,這下終於知道為什麼加時間限制了,資料量過大!!!程式處理不了,改程式碼吧。 雖說實際工作中很少有百萬資料匯入excel,但不缺少一些會excel的高手,分析對比資料,像我這種手殘黨是不行,他們怎麼用暫時不用管,能不能實現,就是我們應該考慮的事了。
簡單介紹下我的操作:
1.HSSFWorkbook 和SXSSFWorkbook區別
HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,副檔名是.xls,一張表最大支援65536行資料,256列,也就是說一個sheet頁,最多匯出6w多條資料
XSSFWorkbook:是操作Excel2007-2010的版本,副檔名是.xlsx對於不同版本的EXCEL文件要使用不同的工具類,如果使用錯了,
會提示如下錯誤資訊。
org.apache.poi.openxml4j.exceptions.InvalidOperationException
org.apache.poi.poifs.filesystem.OfficeXmlFileException
它的一張表最大支援1048576行,16384列,關於兩者介紹,對下面匯出百萬資料很重要,不要使用錯了!
2.使用SXSSFWorkbook物件,匯出百萬資料
SXSSFWorkbook使用方法和 HSSFWorkbook差不多,如果你之前和我一樣用的HSSFWorkbook,現在想要修改,則只需要將HSSFWorkbook改成SXSSFWorkbook即可,下面有我介紹,具體使用也可參考API。
3.如何將百萬資料分成多個sheet頁,匯出到excel
匯出百萬資料到excel,很簡單,只需要將原來的HSSFWorkbook修改成SXSSFWorkbook,或者直接使用SXSSFWorkbook物件,它是直接用來匯出大資料用的,
這裡給出部分程式碼,供參考研究,分頁已實現:
@SuppressWarnings({ "deprecation", "unchecked" }) @RequestMapping("export-TrainHistoryRecord") @ResponseBody protected void buildExcelDocument(EmployeeTrainHistoryQuery query,ModelMap model, SXSSFWorkbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { try { response.reset(); // 獲得國際化語言 RequestContext requestContext = new RequestContext(request); String CourseCompany = requestContext .getMessage("manage-student-trainRecods"); response.setContentType("APPLICATION/vnd.ms-excel;charset=UTF-8"); // 注意,如果去掉下面一行程式碼中的attachment; 那麼也會使IE自動開啟檔案。 response.setHeader( "Content-Disposition", "attachment; filename=" + java.net.URLEncoder.encode( DateUtil.getExportDate() + ".xlsx", "UTF-8"));//Excel 副檔名指定為xlsx SXSSFWorkbook物件只支援xlsx格式 OutputStream os = response.getOutputStream(); CellStyle style = workbook.createCellStyle(); // 設定樣式 style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);//設定單元格著色 style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); //設定單元格填充樣式 style.setBorderBottom(HSSFCellStyle.BORDER_THIN);//設定下邊框 style.setBorderLeft(HSSFCellStyle.BORDER_THIN);//設定左邊框 style.setBorderRight(HSSFCellStyle.BORDER_THIN);//設定右邊框 style.setBorderTop(HSSFCellStyle.BORDER_THIN);//上邊框 style.setAlignment(HSSFCellStyle.ALIGN_CENTER);// 居中 //獲取國際化檔案 String employeeCode = requestContext.getMessage("employeeCode"); String employeeName = requestContext.getMessage("employeeName"); String orgName = requestContext.getMessage("orgName"); String startDate = requestContext.getMessage("start.date"); String endDate = requestContext.getMessage("end.date"); String courseCode = requestContext.getMessage("courseCode"); String courseName = requestContext.getMessage("courseName"); String sessionName = requestContext.getMessage("sessionName"); List<EmployeeTrainHistoryModel> list = null; try { //查詢資料庫中共有多少條資料 query.setTotalItem(employeeTrainHistoryService.fetchCountEmployeeTrainHistoryByQuery(query)); int page_size = 100000;// 定義每頁資料數量 int list_count =query.getTotalItem(); //總數量除以每頁顯示條數等於頁數 int export_times = list_count % page_size > 0 ? list_count / page_size + 1 : list_count / page_size; //迴圈獲取產生每頁資料 for (int m = 0; m < export_times; m++) { query.setNeedQueryAll(false); query.setPageSize(100000);//每頁顯示多少條資料 query.setCurrentPage(m+1);//設定第幾頁 list=employeeTrainHistoryService.getEmployeeTrainHistoryByQuery(query); //新建sheet Sheet sheet = null; sheet = workbook.createSheet(System.currentTimeMillis() + CourseCompany+m); // 建立屬於上面Sheet的Row,引數0可以是0~65535之間的任何一個, Row header = sheet.createRow(0); // 第0行 // 產生標題列,每個sheet頁產生一個標題 Cell cell; String[] headerArr = new String[] { employeeCode, employeeName, orgName, startDate, endDate, courseCode, courseName, sessionName, hoursNunber }; for (int j = 0; j < headerArr.length; j++) { cell = header.createCell((short) j); cell.setCellStyle(style); cell.setCellValue(headerArr[j]); } // 迭代資料 if (list != null && list.size() > 0) { int rowNum = 1; for (int i = 0; i < list.size(); i++) { EmployeeTrainHistoryModel history=list.get(i); sheet.setDefaultColumnWidth((short) 17); Row row = sheet.createRow(rowNum++); row.createCell((short) 0).setCellValue( history.getEmployeeCode()); row.createCell((short) 1).setCellValue( history.getEmployeeName()); row.createCell((short) 2) .setCellValue(history.getOrgName()); if (history.getTrainBeginTime() != null) { row.createCell((short) 3).setCellValue( DateUtil.toString(history.getTrainBeginTime())); } else { row.createCell((short) 3).setCellValue(""); } if (history.getTrainEndTime() != null) { row.createCell((short) 4).setCellValue( DateUtil.toString(history.getTrainEndTime())); } else { row.createCell((short) 4).setCellValue(""); } row.createCell((short) 5).setCellValue( history.getCourseCode()); row.createCell((short) 6).setCellValue( history.getCourseName()); row.createCell((short) 7).setCellValue( history.getSessionName()); if (history.getHoursNumber() != null) row.createCell((short) 8).setCellValue( history.getHoursNumber().toString()); } } list.clear(); } } catch (Exception e) { e.printStackTrace(); } try { workbook.write(os); os.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } }
4.如何高效匯出資料
第3部分,大資料量匯出資料,分頁都已實現,但怎樣才能去壓榨時間,高效匯出?Apache POI既然提供了匯出excel的方法,想必也考慮到了效率問題,檢視官方文件 , 果不其然,看文件,大概意思就是說SXSSF在必須生成大型電子表格時使用,堆空間有限 官方提供了2種方法:
1. SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
2.SXSSFWorkbook wb = new SXSSFWorkbook(-1); // turn off auto-flushing and accumulate all rows in memory
值100 在記憶體中保留100行,超過行將被重新整理到磁碟
值-1表示無限制訪問。 在這種情況下所有,沒有被呼叫flush()重新整理的記錄可用,用於隨機訪問。
文章在最後說,當臨時檔案過大時,可使用setCompressTempFiles方法進行壓縮,
比較貪心,這裡我用了兩個,一個用來設定臨時檔案,另一個用來輸入資料,測試資料為30w資料,結果如圖,不過還是感覺花費時間太多,不知道是不是我的程式寫的有問題,知道的小夥伴,留個言吧!