SpringBoot結合POI百萬級數據報表操作

POI報表高級操作

上篇文章已經介紹Excel可以分爲Excel2003和Excel2007兩種版本,Excel2003在POI中使用HSSF對象,一個sheet最多允許65536條數據,處理較少數據時可以使用,但是處理百萬數據時Excel2003肯定容納不了;Excel2007在POI中使用XSSF對象,最多允許一個sheet存儲1048576條數據,表示其已經可以支持百萬數據,但是在實際運行可能中還存在問題,原因是POI報表所產生的對象,單元格對象,字體對象,都不會銷燬,導致了可能存在OutOfMemoryError(OOM)內存溢出風險。

百萬數據報表導出概述

對於百萬數據Excel的導出,基本只限於討論Excel2007版本的使用方法。ApachePOI提供三種方式解決大數據量的數據導入導出:

  • 用戶模式:用戶模式有許多封裝好的方法,易於操作,但創建對象太多,內存消耗巨大
  • 事件模式:基於SAX(Simple API for XML)方式解析XML,是一個接口,也是一個軟件包,XML解析的一種替代方式,和DOM解析不同點在於逐行解析,不一次性將數據全部加載到內存,相當於一邊掃面一邊解析。
  • SXSSF對象:是用於生成海量Excel的文件,主要藉助臨時存儲空間生成Excel

    百萬數據的導出

需求分析

在互聯網時代,百萬數據經常產生,有多種多樣的原因需要數據導出

解決方式

  • 1.思路分析:

使用POI的XSSFWORK對象進行導出Excel報表,是將所有的數據一次性到單元格對象,並保存到內存中,當單元格全部創建完成纔會一次性寫入到Excel進行導出。當達到百萬級別的數據導出時,隨着單元格對象的不斷創建,內存中的數據越來越多,直到OOM。POI的SXSSFWORK對象,是專門用於處理大數據量的Excel導出的。

2.原理分析:

在實例化SXSSFWork對象時,可以指定內存中所產生的POI對象數量(默認100),一旦內存對象個數達到指定數量,就將內存中的數據寫入到磁盤,就可以將內存中數據進行銷燬,以此循環直到Excel導出完成

代碼實現

還是以用戶數據爲例子

在FileUtil中添加

對之前的做了一些小改動

package com.cn.greemes.common.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 文件操作
 */
public class FileUtil {

    public static final String SYS_TEM_DIR =System.getProperty("java.io.tmpdir")+ File.separator;

    public  void downloadExcel(List<Map<String, String>> list, HttpServletResponse response) throws IOException {
        String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
        File file = new File(tempPath);
        BigExcelWriter writer = ExcelUtil.getBigWriter(file);
        // 一次性寫出內容,使用默認樣式,強制輸出標題
        writer.write(list, true);
        SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();
        //上面需要強轉SXSSFSheet  不然沒有trackAllColumnsForAutoSizing方法
        sheet.trackAllColumnsForAutoSizing();
        //列寬自適應
       // writer.autoSizeColumnAll();
        //response爲HttpServletResponse對象
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        //test.xls是彈出下載對話框的文件名,不能爲中文,中文請自行編碼
        response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
        ServletOutputStream out = response.getOutputStream();
        // 終止後刪除臨時文件
        file.deleteOnExit();
        writer.flush(out, true);
        //此處記得關閉輸出Servlet流
        IoUtil.close(out);
    }

    public  void downloadExcelBySXSSF(List<Map<String, String>> list, HttpServletResponse response) throws IOException {
        String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
        File file = new File(tempPath);
        //2.創建工作簿
         SXSSFWorkbook workbook = new SXSSFWorkbook();
        //3.構造sheet
        Sheet sheet =workbook.createSheet();
        //創建表頭
        Row row = sheet.createRow(0);
        Map<String,String> mapfirst = list.get(0);

        String listHead = null;
        AtomicInteger headersAi = new AtomicInteger();

        for (String key : mapfirst.keySet()) {
           Cell cell = row.createCell(headersAi.getAndIncrement());

           cell.setCellValue(key);
        }
        AtomicInteger datasAi = new AtomicInteger(1);
        Cell cell =null;
        for(Map<String, String> map : list){
            Row dataRow = sheet.createRow(datasAi.getAndIncrement());
            int i=0;
            for (String key : map.keySet()) {
                Cell cell1 = dataRow.createCell(datasAi.getAndIncrement());
                String value= (String)map.get(key);
                cell = dataRow.createCell(i);
                cell1.setCellValue(value);
                i++;
            }
        }
        String fileName = URLEncoder.encode("用戶信息.xlsx", "UTF-8");
        response.setContentType("application/octet-stream");
        response.setHeader("content-disposition", "attachment;filename=" + new
                String(fileName.getBytes("ISO8859-1")));
        response.setHeader("filename", fileName);
        workbook.write(response.getOutputStream());

    }
}

在controller進行配置

 @ApiOperation("導出用戶數據")
    @RequestMapping(value = "/export2", method = RequestMethod.GET)
    @ResponseBody
    public void export2(HttpServletResponse response, @RequestParam(value = "keyword", required = false) String keyword,
                       @RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
                       @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) throws UnsupportedEncodingException, IOException {
        Page<MesAdmin> adminList = adminService.list(keyword, pageSize, pageNum);

        List<Map<String,String>> list = new ArrayList();
        //因爲只有七條數據,所以做了多次循環添加數據
        for(int i=0;i<149795;i++) {
            for (MesAdmin umsAdmin : adminList.getRecords()) {
                Map<String, String> map = new HashMap<>(6);
                DateFormat d1 = DateFormat.getDateInstance();
                map.put("姓名", umsAdmin.getUsername());
                map.put("郵箱", umsAdmin.getEmail());
                map.put("暱稱", umsAdmin.getNickName());
                map.put("備註信息", umsAdmin.getNote());
                map.put("創建時間", d1.format( umsAdmin.getCreateTime()));
                String loginTime ="";
                if(umsAdmin.getLoginTime()!=null){
                    loginTime=d1.format( umsAdmin.getLoginTime());
                }
                map.put("最後登錄時間",loginTime );
                list.add(map);
            }
        }

        fileUtil.downloadExcelBySXSSF(list,response);
    }

結束語:

本來要介紹數據的導出,可是發現百萬級別的數據導出也需要介紹一下,明天介紹數據的導出

Github地址:

github地址:https://github.com/bangbangzhou/greemes/tree/master

公衆號

SpringBoot結合POI百萬級數據報表操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章