1. 程式人生 > >java 分頁匯出百萬級資料到excel,分頁實現

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物件,它是直接用來匯出大資料用的,

官方文件 有介紹,但是如果有300w條資料,一下匯入一個excel的sheet頁中,想想開啟excel也需要一段時間吧,慢的話有可能導致程式無法載入,或者直接結束程序的情況發生,曾看到過一段新聞 ,這裡對老外的毅力也是深表佩服。

這裡給出部分程式碼,供參考研究,分頁已實現:

複製程式碼
@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資料,結果如圖,不過還是感覺花費時間太多,不知道是不是我的程式寫的有問題,知道的小夥伴,留個言吧!