百萬甚至千萬級數據下載


(#)直奔主題,這次要說的是針對百萬千萬數據的下載,使用的是poi excel的下載,這個如果大家不熟悉的話,可以去看看這個基礎博客,寫的不錯

  http://www.jerehedu.com/fenxiang/160218_for_detail.htm

 

(#)然而問題來了,(1)excel如何裝這么多的數據呢?(2)jvm肯定是一次放不下的  針對與問題(1),其實比較好解決,excel07提供了一個新的

   XSSFWorkbook,它會將數據存儲在磁盤上,內存中只加載一部分夠自己使用就好,那么對於問題(2)怎么解決呢?其實想想也不是很難,只要使用

   lazy的加載模式就沒有問題了(lazy模式只會在使用的時候才將數據加載),那么在java中lazy的模式首先想到的就是Iterable來包裝

 

(#)這里如果大家的tomcat沒有打印gc.log日志,因為這個代碼運行的時候,首先要關注的就是gc的問題,如果發現一直在fgc那么肯定就要跪了,這個時候怎么看gc

  日志呢?嗯,如果不清楚的話,首先要做的就是去熟悉一下jvm帶的命令,我們使用jstat -gcutil -uid 來看一下,如下:

 

(#) 廢話不多說,show the  code

  首先是讀取數據庫數據的代碼,如下:

   Iterable<List<AirlineRecord>> airRecords = new Iterable<List<AirlineRecord>>() {
            @Override
            public Iterator<List<AirlineRecord>> iterator() {
                return new AbstractIterator<List<AirlineRecord>>() {
                    int totalCount = airlineRecordDao.selectCountByState(null);
                    int sizeCount = totalCount / SIZE;
                    int index = 0;

                    @Override
                    protected List<AirlineRecord> computeNext() {
                        if (index > sizeCount) {
                            return endOfData();
                        }
                        List<AirlineRecord> records = airlineRecordDao.selectByState(null, new RowBounds(index * SIZE, SIZE));
                        index++;
                        return records;
                    }
                };
            }
        };

  我的數據庫中的數據量是100多萬的,RowBounds應該使用一個攔截器改成物理分,要不還是會有問題的,然后剩下的就很簡單了,利用Iterable本身的lazy屬性,現

  在不會將多有的數據都加載進來,每次只會迭代SIZE大小的數據,這樣JVM就不會有任何壓力,百萬,千萬的數據都是可以撐的住的

下面來看一下下載的代碼

 public static <T> SXSSFWorkbook export(Iterable<T> excelData, String sheetName, TableFormatterConfigurer configurer) {
        // 聲明一個(SXSSFWorkbook)工作薄
        SXSSFWorkbook workbook = new SXSSFWorkbook(); // SXSSFWorkbook默認只在內存中存放100行
        T firstItem = Iterables.getFirst(excelData, null);
        if (firstItem != null) {
            return export(sheetName, excelData, workbook, configurer, (Class<T>) firstItem.getClass());
        }
        return workbook;
    }

    /**
     * 導出Excel
     *
     * @param sheetName excel表格名
     * @param excelData 要導出的數據
     * @param workbook 要導出的工作薄
     * @return
     *
     * @author mars.mao created on 2014年10月17日下午7:36:26
     */
    public static <T> HSSFWorkbook export(String sheetName, List<T> excelData, HSSFWorkbook workbook) {
        return export(sheetName, excelData, workbook, TableFormatterConfigurer.NONE);
    }

    public static <T> HSSFWorkbook export(String sheetName, List<T> excelData, HSSFWorkbook workbook,
            TableFormatterConfigurer configurer) {

        if (excelData == null || excelData.isEmpty() || workbook == null || StringUtils.isBlank(sheetName)) {
            return workbook;
        }

        try {

            // 定義標題行字體
            Font font = workbook.createFont();
            font.setBoldweight(Font.BOLDWEIGHT_BOLD);

            int totalDataSize = excelData.size();
            int sheetCnt = totalDataSize / EXCEL_MAX_ROW_NO + 1;

            if (sheetCnt > EXCEL_MAX_SHEET_CNT) {
                throw new Exception("數據量超過了Excel的容量范圍!");
            }

            for (int i = 0; i < sheetCnt; i++) {
                int fromIndex = i * EXCEL_MAX_ROW_NO;
                int toIndex = fromIndex + EXCEL_MAX_ROW_NO;
                toIndex = toIndex > totalDataSize ? totalDataSize : toIndex;
                List<T> sheetData = excelData.subList(fromIndex, toIndex);

                // 生成一個表格
                Sheet sheet = workbook.createSheet(sheetName + "_" + i);

                // 生成標題行
                createHeader(sheetData, sheet, font, configurer);

                // 遍歷集合數據,產生數據行
                createBody(sheetData, sheet, configurer);
            }

            logger.info("導出的數據行數(不含表頭): writeCount={}", excelData.size());
            return workbook;
        } catch (Exception e) {
            logger.error("導出Excel異常!", e);
        }

        return workbook;
    }

    /**
     * 導出Excel, 使用SXSSFWorkbook支持大數據量的導出
     *
     * @param sheetName excel表格名
     * @param excelData 要導出的數據
     * @param workbook 要導出的工作薄
     * @param configurer 配置數據格式化行為的對象
     * @param clazz 待導出數據的類型信息
     * @return
     */
    public static <T> SXSSFWorkbook export(String sheetName, Iterable<T> excelData, SXSSFWorkbook workbook,
            TableFormatterConfigurer configurer, Class<T> clazz) {
        final Iterator<T> externalIterator;
        if (excelData == null || !(externalIterator = excelData.iterator()).hasNext() || workbook == null
                || StringUtils.isBlank(sheetName)) {
            return workbook;
        }

        try {
            // 定義標題行字體
            Font font = workbook.createFont();
            font.setBoldweight(Font.BOLDWEIGHT_BOLD);

            int writeCount = 0; // 記錄導出的行數
            int i = 0;
            while (externalIterator.hasNext()) {
                // 生成一個表格
                Sheet sheet = workbook.createSheet(sheetName + "_" + i);

                // 生成標題行
                createHeader(sheet, font, configurer, clazz);

                // 遍歷集合數據,產生數據行
                writeCount += createBody(new Iterable<T>() {
                    public Iterator<T> iterator() {
                        return new Iterator<T>() {
                            int lineCount = 0;

                            public boolean hasNext() {
                                return lineCount < EXCEL_MAX_ROW_NO && externalIterator.hasNext();
                            }

                            public T next() {
                                if (!hasNext()) {
                                    throw new NoSuchElementException("沒有更多的元素");
                                }
                                lineCount++;
                                return externalIterator.next();
                            }

                            public void remove() {
                                throw new UnsupportedOperationException("不支持刪除操作");
                            }
                        };
                    }
                }, sheet, configurer, clazz);

                if ((++i) == EXCEL_MAX_SHEET_CNT) {
                    throw new Exception("數據量超過了Excel的容量范圍!");
                }
            }

            logger.info("導出的數據行數(不含表頭): writeCount={}", writeCount);
            return workbook;
        } catch (Exception e) {
            logger.error("導出Excel異常!", e);
        }

        return workbook;
    }

    /**
     * 創建表格數據
     *
     * @param excelData 要導出的數據
     * @param sheet excel表格
     * @param configurer 配置數據格式化行為的對象
     * @return 返回創建的數據行數
     *
     * @author mars.mao created on 2014年10月17日下午3:43:43
     */
    @SuppressWarnings("unchecked")
    private static <T> int createBody(List<T> excelData, Sheet sheet, TableFormatterConfigurer configurer)
            throws Exception {
        if (CollectionUtils.isEmpty(excelData)) {
            return 0;
        }

        Class<?> dataClass = excelData.get(0).getClass();
        return createBody(excelData, sheet, configurer, dataClass);
    }

    /**
     * 創建表格數據
     *
     * @param excelData 要導出的數據
     * @param sheet excel表格
     * @param configurer 配置數據格式化行為的對象
     * @param clazz 要導出的對象的類型信息, 用以獲取相關注解以及字段值
     * @return 返回創建的數據行數
     */
    private static <T> int createBody(Iterable<T> excelData, Sheet sheet, TableFormatterConfigurer configurer,
            Class<?> clazz) throws Exception {
        if (excelData == null) {
            return 0;
        }
        List<Field> fields = getExportableFields(clazz, configurer);
        Map<String, Method> methodMap = getFormattingMethods(clazz);

        int dataRowIndex = 1;
        for (T data : excelData) {
            // 創建數據行
            Row dataRow = sheet.createRow(dataRowIndex);

            int columnIndex = 0;
            for (Field field : fields) {
                ExcelColumn columnHeader = field.getAnnotation(ExcelColumn.class);
                // 創建列
                String textValue;
                Method formattingMethod = methodMap.get(field.getName());
                if (formattingMethod != null) {
                    // 優先使用"格式化方法"對屬性進行格式化
                    formattingMethod.setAccessible(true);
                    textValue = String.valueOf(formattingMethod.invoke(data));
                } else {
                    // 在沒有"格式化方法"時使用@ExcelColumn指定的格式化方式
                    textValue = getTextValue(data, field, columnHeader, configurer);
                }
                Cell cell = dataRow.createCell(columnIndex);
                // HSSFRichTextString text = new HSSFRichTextString(textValue);
                // cell.setCellValue(text);
                cell.setCellValue(textValue);

                columnIndex++;
            }

            dataRowIndex++;
        }

        return dataRowIndex - 1;
    }

    /**
     * 生成Excel的標題行
     *
     * @param excelData 導出的數據列表
     * @param sheet excel表
     * @return
     *
     * @author mars.mao created on 2014年10月17日下午2:08:41
     */
    @SuppressWarnings("unchecked")
    private static <T> void createHeader(List<T> excelData, Sheet sheet, Font font, TableFormatterConfigurer configurer) {
        if (CollectionUtils.isEmpty(excelData)) {
            return;
        }

        Class<?> dataClass = excelData.get(0).getClass();
        createHeader(sheet, font, configurer, dataClass);
    }

    private static void createHeader(Sheet sheet, Font font, TableFormatterConfigurer configurer, Class<?> clazz) {
        List<Field> fields = getExportableFields(clazz, configurer);

        Row headerRow = sheet.createRow(0);
        int columnIndex = 0;
        for (Field field : fields) {
            ExcelColumn columnHeader = field.getAnnotation(ExcelColumn.class);
            // 獲取指定的列標題和列寬度
            String columnTitle = columnHeader.headerName();
            int columnWidth = columnHeader.columnWidth();

            // 創建列
            Cell cell = headerRow.createCell(columnIndex);
            // 設置列標題
            if (sheet instanceof HSSFSheet) {
                RichTextString text = new HSSFRichTextString(columnTitle);
                text.applyFont(font);
                cell.setCellValue(text);
            } else {
                cell.setCellValue(columnTitle);
            }
            // 設置列寬度
            sheet.setColumnWidth(columnIndex, columnWidth * 256);

            columnIndex++;
        }
    }

    /**
     * 獲取格式化的文本內容
     *
     * @param obj 輸入對象
     * @param field 對象域
     * @param columnHeader ExcelColumn注解的配置實例
     * @param configurer TableFormatterConfigurer的配置實例
     * @return 格式化后的文本
     */
    @SuppressWarnings("unchecked")
    private static <T> String getTextValue(T obj, Field field, ExcelColumn columnHeader,
            TableFormatterConfigurer configurer) {
        String aimPattern = columnHeader.pattern();
        Object fieldValue = getFieldValue(obj, field); // 反射獲取字段的值
        String textValue = null;

        Class<? extends Formatter> formatterClass = columnHeader.formatterClass();
        boolean allowAlternativeFormatter = columnHeader.allowAlternativeFormatter();

        boolean formatted = false;
        try {
            // 1) 嘗試使用注冊在的formatterMap中的formatter; 以屬性名為key的formatterMap優先
            if (allowAlternativeFormatter) {
                Formatter registeredFormatter = configurer.getFormatter(field.getName());
                if (registeredFormatter == null && fieldValue != null) {
                    registeredFormatter = configurer.getFormatter(fieldValue.getClass());
                }
                if (registeredFormatter != null) {
                    textValue = registeredFormatter.format(fieldValue);
                    formatted = true;
                }
            }
            // 2) 沒有獲取到注冊的formatter, 則嘗試使用指定的formatterClass
            if (!formatted && formatterClass != Formatter.None.class) {
                Formatter formatter = formatterClass.newInstance();
                textValue = formatter.format(fieldValue);
                formatted = true;
            }
        } catch (Exception e) {
            logger.error("導出Excel使用formatter格式化出錯: {}", e.getMessage(), e);
            formatted = false;
        }
        if (!formatted) {
            /*
             * 3) [未指定formatter, 且未開啟formatter的注冊] 或者 [以上格式化失敗]; 使用默認的處理方式...
             */
            textValue = defaultToString(fieldValue, aimPattern);
        }
        return textValue;
    }

    /*
     * 默認的格式處理方式 (與歷史版本兼容)
     */
    private static String defaultToString(Object fieldValue, String aimPattern) {
        String textValue = " ";
        if (fieldValue != null) {
            textValue = fieldValue.toString();
        }
        if (fieldValue instanceof Date) {
            try {
                String pattern = StringUtils.isBlank(aimPattern) ? DEFAULT_DATE_FORMAT : aimPattern;
                Date date = (Date) fieldValue;
                textValue = DateFormatUtils.format(date, pattern);
            } catch (Exception e) {
                logger.error("導出Excel日期格式化錯誤!", e);
            }
        } else if (fieldValue instanceof DateTime) { // 添加對DateTime類型的兼容(采用與Date類型一樣的pattern設置) [by chenjiahua.chen
                                                     // 2016/09/05]
            try {
                String pattern = StringUtils.isBlank(aimPattern) ? DEFAULT_DATE_FORMAT : aimPattern;
                DateTime dateTime = (DateTime) fieldValue;
                textValue = dateTime.toString(pattern);
            } catch (Exception e) {
                logger.error("導出Excel日期格式化錯誤!", e);
            }
        } else if (fieldValue instanceof Money) { // 添加對Money類型的兼容, 以toMoneyString輸出(兩位小數) [by chenjiahua.chen
                                                  // 2016/09/05]
            try {
                Money money = (Money) fieldValue;
                return money.toMoneyString();
            } catch (Exception e) {
                logger.error("導出Excel金額格式化錯誤!", e);
            }
        } else if (fieldValue instanceof Number) {
            if (StringUtils.isNotBlank(aimPattern)) {
                try {
                    double doubleValue = Double.parseDouble(fieldValue.toString());
                    DecimalFormat df1 = new DecimalFormat(aimPattern);
                    textValue = df1.format(doubleValue);
                } catch (Exception e) {
                    logger.error("導出Excel數字格式化錯誤!", e);
                }
            }
        }
        return textValue;
    }

    /**
     * 反射獲取字段的值
     *
     * @param obj 對象
     * @param field 字段
     * @return
     *
     * @author mars.mao created on 2014年10月17日下午2:58:53
     */
    private static <T> Object getFieldValue(T obj, Field field) {
        Object fieldValue = " ";

        try {
            field.setAccessible(true);
            fieldValue = field.get(obj);
            if (fieldValue != null) {
                return fieldValue;
            }
        } catch (Exception e) {
            logger.error("導出Excel動態獲取字段值異常", e);
        }
        return fieldValue;
    }

    /**
     * 根據給定類的信息獲取其所有字段 (包括繼承而來的字段!)
     */
    private static List<Field> getExportableFields(Class<?> clazz, final TableFormatterConfigurer configurer) {
        List<Field> fields = Lists.newArrayList();
        /* 循環向上查找, 以支持父類中的屬性導出 [by chenjiahua.chen 2016/09/05] */
        for (Class<?> dataClass : getClassesInHierarchy(clazz)) {
            fields.addAll(Arrays.asList(dataClass.getDeclaredFields()));
        }

        // 過濾不需要導出的屬性
        fields = Lists.newArrayList(Iterables.filter(fields, new Predicate<Field>() {
            @Override
            public boolean apply(Field field) {
                ExcelColumn columnHeader = field.getAnnotation(ExcelColumn.class);
                if (columnHeader == null || configurer.getHeadersToBeFiltered().contains(columnHeader.headerName())) {
                    return false;
                }
                return true;
            }
        }));
        return fields;
    }

    private static Map<String, Method> getFormattingMethods(Class<?> clazz) {
        List<Method> methods = Lists.newArrayList();
        /* 循環向上查找, 以支持父類中的格式化方法 [by chenjiahua.chen 2017/02/09] */
        for (Class<?> dataClass : getClassesInHierarchy(clazz)) {
            methods.addAll(Arrays.asList(dataClass.getDeclaredMethods()));
        }

        // 過濾和格式化無關的方法
        methods = Lists.newArrayList(Iterables.filter(methods, new Predicate<Method>() {
            @Override
            public boolean apply(Method method) {
                return method.getAnnotation(FieldFormat.class) != null;
            }
        }));

        return Maps.uniqueIndex(methods, new Function<Method, String>() {
            @Override
            public String apply(Method method) {
                return method.getAnnotation(FieldFormat.class).fieldName();
            }
        });
    }

    /**
     * 獲取繼承體系中的類
     */
    private static List<Class<?>> getClassesInHierarchy(Class<?> clazz) {
        List<Class<?>> classes = Lists.newArrayList();

        Stack<Class<?>> classStack = new Stack<Class<?>>();
        Class<?> currentClass = clazz;
        while (currentClass != null) {
            classStack.push(currentClass);
            currentClass = currentClass.getSuperclass();
        }
        while (!classStack.isEmpty()) {
            classes.add(classStack.pop());
        }
        return classes;
    }

    public static Workbook dynamicExport(String sheetName, List<RowRecord> excelData) {
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(excelData) || StringUtils.isBlank(sheetName)) {
            return null;
        }
        HSSFWorkbook workbook = new HSSFWorkbook();
        // 標題行字體
        Font font = workbook.createFont();
        font.setBold(true);
        // 定義單元格格式
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 分割sheet
        int totalCnt = excelData.size();
        int sheetCnt = totalCnt / EXCEL_MAX_ROW_NO + 1;
        if (sheetCnt > EXCEL_MAX_SHEET_CNT) {
            throw new RuntimeException("數據量超過了Excel的容量范圍!");
        }

        for (int i = 0; i < sheetCnt; i++) {
            int fromIndex = i * EXCEL_MAX_ROW_NO;
            int toIndex = fromIndex + EXCEL_MAX_ROW_NO;
            toIndex = toIndex > totalCnt ? totalCnt : toIndex;
            List<RowRecord> sheetData = excelData.subList(fromIndex, toIndex);
            // 生成一個表格
            Sheet sheet = workbook.createSheet(sheetName + "_" + i);
            // 生成標題行
            createHeader(sheetData, sheet, font, cellStyle);
            // 遍歷集合數據,產生數據行
            createBody(sheetData, sheet);
        }

        return workbook;
    }

    private static void createHeader(List<RowRecord> excelData, Sheet sheet, Font font, CellStyle cellStyle) {

        if (org.apache.commons.collections4.CollectionUtils.isEmpty(excelData)) {
            return;
        }
        RowRecord rowRecord = excelData.get(0);

        createHeader(sheet, rowRecord, font, cellStyle);
    }

    private static void createHeader(Sheet sheet, RowRecord rowRecord, Font font, CellStyle cellStyle) {
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(rowRecord.getColumnRecordList())) {
            return;
        }
        // 獲取表頭節點的層數
        List<ColumnRecord> columnRecordList = rowRecord.getColumnRecordList();
        int HeaderLayerCnt = 1;
        for (ColumnRecord columnRecord : columnRecordList) {
            if (HeaderLayerCnt < columnRecord.getColumnNameList().size()) {
                HeaderLayerCnt = columnRecord.getColumnNameList().size();
            }
        }
        int i = 0;
        int columnIndex;
        int preColumnIndex;
        String preHeader;
        while (i < HeaderLayerCnt) {
            preHeader = null;
            preColumnIndex = columnIndex = 0;
            Row headerRow = sheet.createRow(i);
            for (ColumnRecord columnRecord : rowRecord.getColumnRecordList()) {
                if (columnRecord.getColumnNameList().size() < i + 1) {
                    columnIndex++;
                    preColumnIndex++;
                    continue;
                }
                if (preHeader == null || !columnRecord.getColumnNameList().get(i).equals(preHeader)) {
                    // 獲取指定的列標題和列
                    if (columnIndex - preColumnIndex > 1) {
                        sheet.addMergedRegion(new CellRangeAddress(i, i, preColumnIndex, columnIndex - 1));
                    }
                    preColumnIndex = columnIndex;
                    String columnTitle = columnRecord.getColumnNameList().get(i);
                    // 創建列
                    Cell cell = headerRow.createCell(columnIndex);
                    // 設置列標題
                    RichTextString text = new HSSFRichTextString(columnTitle);
                    text.applyFont(font);
                    cell.setCellValue(text);
                    cell.setCellStyle(cellStyle);
                    preHeader = columnRecord.getColumnNameList().get(i);
                }
                // 最后一列判定
                if ((columnIndex == rowRecord.getColumnRecordList().size() - 1) && (columnIndex - preColumnIndex >= 1)) {
                    sheet.addMergedRegion(new CellRangeAddress(i, i, preColumnIndex, columnIndex));
                }
                columnIndex++;
            }
            i++;
        }

    }

    private static void createBody(List<RowRecord> rowRecords, Sheet sheet) {
        int rowIndex = sheet.getLastRowNum() + 1;
        for (RowRecord rowRecord : rowRecords) {
            Row dataRow = sheet.createRow(rowIndex);
            int columnIndex = 0;
            for (ColumnRecord columnRecord : rowRecord.getColumnRecordList()) {
                String textValue = getTextValue(columnRecord);
                Cell cell = dataRow.createCell(columnIndex);
                cell.setCellValue(textValue);
                sheet.autoSizeColumn(columnIndex, true);
                columnIndex++;
            }
            rowIndex++;
        }
    }

    private static String getTextValue(ColumnRecord columnRecord) {
        Class<? extends Formatter> formatterClass = columnRecord.getFormatter();
        if (formatterClass.equals(Formatter.DefaultFormatter.class)) {
            return columnRecord.getPropertyValue().toString();
        }
        try {
            Formatter<Object> formatter = formatterClass.newInstance();
            return formatter.format(columnRecord.getPropertyValue());
        } catch (Exception e) {
            logger.error("導出Excel使用formatter格式化出錯: {}", e.getMessage(), e);
        }

        return null;
    }

  說點啥呢,嗯,就隨便說說如果操作不當的時候會出現什么情況呢,首先看看gc,會一直在進行fgc,但是並沒有什么卵用,E 100%,O 99.99%,每次fgc由於數據都是被

  引用的狀態,也就是說根本gc不掉的,那么就會一直進行fgc,fgc的時候是stop the world,所以你的tomcat會出在假死狀態,並且肯定會造成服務不可用,一會服務器

    就會真的跪了,所以寫代碼的時候一定要注意你的內存使用情況,注意數據的大小.

 

  最后說下,我導出了100多萬的數據,沒啥問題,理論上導出千萬的數據也沒有問題,只是會分成多個sheet而已,如果使用上有什么問題,歡迎咨詢可以一起討論!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM