zoukankan      html  css  js  c++  java
  • 百万甚至千万级数据下载

    (#)直奔主题,这次要说的是针对百万千万数据的下载,使用的是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而已,如果使用上有什么问题,欢迎咨询可以一起讨论!

  • 相关阅读:
    【原创】tyvj1038 忠诚 & 计蒜客 管家的忠诚 & 线段树(单点更新,区间查询)
    [转载]线段树模板
    并查集相当6的一篇文章~~~
    觉得一篇讲SPFA还不错的文章
    【原创】POJ 3259 Wormholes(Bellman-Ford) && 简介Bellman-Ford算法
    【原创】谈谈数据结构课后作业......尴尬不已...《图》 后面的迷宫问题
    MyBatis源码分析(七):动态代理(Mybatis核心机制)
    1026: [SCOI2009]windy数
    Spring Boot 官方文档学习(一)入门及使用
    Spring Hibernate JPA 联表查询 复杂查询
  • 原文地址:https://www.cnblogs.com/wscit/p/6812231.html
Copyright © 2011-2022 走看看