Java POI导出百万级别Excel数据的工具类

为了更方便快捷地实现Excel导出数据,之前利用Kendo UI地Excel导出grid数据,非常方便快捷,但是却发现无法导出大量数据,所以需要导出百万级别地数据时只能后台java代码实现。通过查询资料得知,java导出excel的一般使用jxl或POI来实现,但jxl已经不更新,且无法支持Excel 2007版本(sheet的最大行数是1048576),现在一般使用POI导出。网上有很多关于使用POI的例子,这里不再赘述。


为了方便地实现后台开发,而且满足导出百万级别数据,我开发了一个POI 导出Excel的工具类(参考了他人代码)。经过我的测试,72万条数据,查询需要7、8分钟,主要是将数据从数据库传到服务器的网络耗时,POI处理数据大概需要35秒,可以接受。(吐槽下,为啥我优化代码尽量避免不必要的代码重复运行,实际却没有看到运行时间明显减少,反而让代码可读性降低了。)同时也说明,查询时应尽量避免查询不必要的数据,导致数据库传输大量数据耗时。
代码在发布到linux时,POI导出Excel可能会出现java.lang.NoClassDefFoundError: sun/awt/X11GraphicsEnvironment异常。解决方案: 在tomcat配置文件catalina.sh文件中添加 CATALINA_OPTS=”-Djava.awt.headless=true”


工具类源代码:

package utils;

import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletResponse;

import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import core.exception.ExcelException;

/**   
 * @ClassName:  ExcelPoiUtil   
 * @Description:使用POI对Excel进行操作
 *  现阶段只实现了将List导出Excel至浏览器:listToExcel
 * @author: chong.luo
 * @version 1.0   
 */
public class ExcelPoiUtil {

    /**   
     *Excel 2007最大行数1048576
     */ 
    private static final int SHEET_MAX_SIZE_XLSX=1048576;
    /**   
     * Sheet默认列宽,excel的列宽
     */ 
    private static final int SHEET_DEFAULT_COLUMN_WIDTH=18;
    /*   
     * Sheet默认行高,excel的行高
     */ 
    //private static final float SHEET_DEFAULT_ROW_HEIGHT_POINTS = 16;

    /**     
     * @Description: 导出Excel(导出到浏览器) 
     * @param list 数据源 (仅支持dto和vo)
     * @param fieldMap 类的英文属性和Excel中的中文列名的对应关系 
     * @param response 使用response可以导出到浏览器 
     * @param fileName 文件名(建议加上日期后缀)
     * @throws ExcelException 
     * @author: chong.luo   
     */
    public static  <T>  void  listToExcel (  
            List<T> list,  
            LinkedHashMap<String,String> fieldMap, 
            HttpServletResponse response,
            String fileName            
            ) throws ExcelException{  

        //设置response头信息  
        response.reset();
        response.setContentType("application/octet-stream;charset=utf-8");  
        try {
            response.setHeader("Content-Disposition", "attachment;filename="  
                    + new String(fileName.getBytes(),"iso-8859-1") + ".xlsx");  
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }  
        //创建工作簿并发送到浏览器  
        try {
            OutputStream out=response.getOutputStream();  
            listToExcel(list, fieldMap, SHEET_MAX_SIZE_XLSX-1, out );  

        } catch (Exception e) {  
            e.printStackTrace();
            //如果是ExcelException,则直接抛出  
            if(e instanceof ExcelException){  
                throw (ExcelException)e;  
            //否则将其它异常包装成ExcelException再抛出  
            }else{  
                throw new ExcelException("导出Excel失败");  
            }  
        }  
    }


    /**     
     * @Description: 导出Excel(可以导出到本地文件系统,也可以导出到浏览器,可自定义工作表大小)
     * @param list 数据源 (仅支持dto、vo等)
     * @param fieldMap 类的英文属性和Excel中的中文列名的对应关系 
     * 如果需要的是引用对象的属性,则英文属性使用类似于EL表达式的格式 
     * 如:list中存放的都是student,student中又有college属性,而我们需要学院名称,则可以这样写 
     * fieldMap.put("college.collegeName","学院名称")
     * @param sheetSize 每个工作表中记录的最大个数 
     * @param out 导出流 
     * @throws ExcelException 
     * @author: chong.luo   
     */
    public static <T> void listToExcel (  
            List<T> list ,  
            LinkedHashMap<String,String> fieldMap,
            int sheetSize,  
            OutputStream out  
            ) throws ExcelException{  

        /*if(list.size()==0 || list==null){  
            throw new ExcelException("数据源中没有任何数据");  
        }*/  
        if(sheetSize>SHEET_MAX_SIZE_XLSX-1 || sheetSize<1){  
            sheetSize=SHEET_MAX_SIZE_XLSX-1;  
        }

        //1.建立操作的SXSSFWorkbook相关对象
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);// 内存中只创建100个对象,写临时文件,当超过100条,就将内存中不用的对象释放。
        SXSSFSheet sheet = null; // 工作表对象
        SXSSFRow row = null; // 行对象
        CellStyle headCellStyle = getHeadCellStyle(workbook); //首行单元格样式
        CellStyle cellStyle = getCellStyle(workbook); //单元格样式

        try{
            //2.拆解fieldMap:定义存放英文字段名和中文字段名的数组  
            String[] enFields=new String[fieldMap.size()];  
            String[] cnFields=new String[fieldMap.size()]; 
            //填充数组  
            int count=0;  
            for(Entry<String,String> entry:fieldMap.entrySet()){  
                enFields[count]=entry.getKey();  
                cnFields[count]=entry.getValue();  
                count++;  
            }

            //3.建立一个新的Sheet,并填充表头列
            int rowNo = 0; // 页行号
            sheet = workbook.createSheet();// 建立新的Sheet
            sheet.setDefaultColumnWidth(SHEET_DEFAULT_COLUMN_WIDTH); // 设置默认列宽
            //sheet.setDefaultRowHeightInPoints(SHEET_DEFAULT_ROW_HEIGHT_POINTS); //设置默认列高
            row = sheet.createRow(rowNo);//定义表头
            fillHeadRow(row,cnFields,headCellStyle);//将列数据填充至row中

            //4.一行一行填充数据列
            for(int i = 0,listSize = list.size(); i<listSize; i++){
                //3.如果超过Sheet的行数限制,则建立一个新的Sheet,并填充表头列
                if (i % sheetSize == 0 && i != 0) { // 建立新的Sheet
                    rowNo = 0; // 每当新建了工作表就将当前工作表的行号重置为0
                    sheet = workbook.createSheet();sheet.setDefaultColumnWidth(SHEET_DEFAULT_COLUMN_WIDTH); // 设置默认列宽
                    //sheet.setDefaultRowHeightInPoints(SHEET_DEFAULT_ROW_HEIGHT_POINTS); //设置默认列高
                    row = sheet.createRow(rowNo);//定义表头
                    fillHeadRow(row,cnFields,headCellStyle);
                }
                row = sheet.createRow(++rowNo); //新建行对象
                fillDataRow(row,list.get(i),enFields,cellStyle);//填充数据至行中

            }
            //5.将SXSSFWorkbook数据写入流中
            //System.out.println("-------数据处理完成,当前时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).toString() +"-------");
            workbook.write(out); //写入流中
            //System.out.println("-------数据导出完成,当前时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).toString() +"-------");

        }catch (Exception e) {  
            e.printStackTrace();  
            //如果是ExcelException,则直接抛出  
            if(e instanceof ExcelException){  
                throw (ExcelException)e; 
            //否则将其它异常包装成ExcelException再抛出  
            }else{  
                throw new ExcelException("导出Excel失败");  
            }  
        }


    }

    /**     
     * @Description: 向首行对象中填充列标题(分离首行填充,以提高代码效率)
     * @param row   行对象
     * @param cnFields  首行列标题,来源于fieldMap 
     * @param cellStyle  填充行所用的单元格样式
     * @throws Exception 
     * @author: chong.luo   
     */
    private static <T> void fillHeadRow(
            SXSSFRow row,
            String[] cnFields,
            CellStyle cellStyle
            )throws Exception{
        try{
            for(int i=0;i<cnFields.length;i++){  
                SXSSFCell cell = row.createCell(i);
                cell.setCellValue(cnFields[i]); //设置单元格的值
                cell.setCellStyle(cellStyle); //设置单元格的样式
            }
        }catch (Exception e) {  
            e.printStackTrace();  
            if(e instanceof ExcelException){ //如果是ExcelException,则直接抛出   
                throw (ExcelException)e; 
            }else{  //否则将其它异常包装成ExcelException再抛出  
                throw new ExcelException("导出Excel失败:填充第"+(row.getRowNum()+1)+"行时失败");  
            }  
        }
        return;

    }

    /**     
     * @Description: 向数据行行对象中填充数据
     * @param row   行对象
     * @param item  列数据,dto或vo 
     * @param enFields  类的英文属性 ,来源于fieldMap 
     * @param cellStyle  填充行所用的单元格样式
     * @throws Exception 
     * @author: chong.luo   
     */
    private static <T> void fillDataRow(
            SXSSFRow row,
            T item,
            String[] enFields,
            CellStyle cellStyle
            )throws Exception{
        try{
            // 数据行
            for(int i=0;i<enFields.length;i++){  
                Object objValue=getFieldValueByNameSequence(enFields[i], item);
                //时间类型的值需要格式化一下再转字符串 
                if((objValue instanceof Date) && objValue != null){
                    String dataValue = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(objValue);
                    if(dataValue.endsWith("00:00:00")){
                        dataValue = dataValue.substring(0, 10); //去掉时分秒
                    }
                    objValue = dataValue;
                }
                //设置单元格的值及样式
                String fieldValue=objValue==null ? "" : objValue.toString();  
                SXSSFCell cell = row.createCell(i);
                cell.setCellValue(fieldValue); //设置单元格的值
                cell.setCellStyle(cellStyle); //设置单元格的样式,不启用以提高导出速度
            }
        }catch (Exception e) {  
            e.printStackTrace();  
            //如果是ExcelException,则直接抛出  
            if(e instanceof ExcelException){  
                throw (ExcelException)e; 
            //否则将其它异常包装成ExcelException再抛出  
            }else{  
                throw new ExcelException("导出Excel失败:填充第"+(row.getRowNum()+1)+"行时失败");  
            }  
        }
        return;

    }


    /**     
     * @Description: 获取首行的样式信息
     * @param workbook
     * @return 
     * @author: chong.luo   
     */
    private static CellStyle getHeadCellStyle(SXSSFWorkbook workbook){
        CellStyle headCellStyle = workbook.createCellStyle();
        headCellStyle.setAlignment(HorizontalAlignment.CENTER); //设置居中
        //headCellStyle.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); 
        headCellStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.index);//设置背景色(前景色)
        headCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); //设置背景色必须的

        Font headFont = workbook.createFont(); //字体样式
        //headFont.setBold(true); //设置字体加粗
        headFont.setFontName("宋体"); //字体
        headFont.setColor(IndexedColors.WHITE.index);
        headCellStyle.setFont(headFont);
        return headCellStyle;
    }

    /**     
     * @Description: 获取单元格的样式信息
     * @param workbook
     * @return 
     * @author: chong.luo   
     */
    private static CellStyle getCellStyle(SXSSFWorkbook workbook){
        CellStyle style = workbook.createCellStyle(); //注意java.lang.IllegalStateExceptionYou can define up to 64000 style in a .xlsx Workbook
        //style.setAlignment(HorizontalAlignment.CENTER); //设置居中
        //headCellStyle.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); 
        //style.setFillForegroundColor(IndexedColors.LIGHT_GREEN.index);//设置背景色(前景色)
        //style.setFillPattern(FillPatternType.SOLID_FOREGROUND); //设置背景色必须的

        Font font = workbook.createFont(); //字体样式
        //font.setBold(true); //设置字体加粗
        font.setFontName("宋体"); //字体
        style.setFont(font);
        return style;
    }


    /** 
     * @MethodName  : getFieldValueByNameSequence 
     * @Description :  
     * 根据带路径或不带路径的属性名获取属性值 
     * 即接受简单属性名,如userName等,又接受带路径的属性名,如student.department.name等 
     *  
     * @param fieldNameSequence  带路径的属性名或简单属性名 
     * @param o 对象 
     * @return  属性值 
     * @throws Exception 
     */ 
    private static  Object getFieldValueByNameSequence(String fieldNameSequence, Object o) throws Exception{  

        Object value=null;  

        //将fieldNameSequence进行拆分  
        String[] attributes=fieldNameSequence.split("\\.");  
        if(attributes.length==1){  
            value=getFieldValueByName(fieldNameSequence, o);  
        }else{  
            //根据属性名获取属性对象  
            Object fieldObj=getFieldValueByName(attributes[0], o);  
            String subFieldNameSequence=fieldNameSequence.substring(fieldNameSequence.indexOf(".")+1);  
            value=getFieldValueByNameSequence(subFieldNameSequence, fieldObj);  
        }  
        return value;   

    }   

    /** 
     * @MethodName  : getFieldValueByName 
     * @Description : 根据字段名获取字段值 
     * @param fieldName 字段名 
     * @param o 对象 
     * @return  字段值 
     */ 
    private static  Object getFieldValueByName(String fieldName, Object o) throws Exception{  

        Object value=null;  
        Field field=getFieldByName(fieldName, o.getClass());  

        if(field !=null){  
            field.setAccessible(true);  
            value=field.get(o);  
        }else{  
            throw new ExcelException(o.getClass().getSimpleName() + "类不存在字段名 "+fieldName);  
        }  

        return value;  
    }  

    /** 
     * @MethodName  : getFieldByName 
     * @Description : 根据字段名获取字段 
     * @param fieldName 字段名 
     * @param clazz 包含该字段的类 
     * @return 字段 
     */ 
    private static Field getFieldByName(String fieldName, Class<?>  clazz){  
        //拿到本类的所有字段  
        Field[] selfFields=clazz.getDeclaredFields();  

        //如果本类中存在该字段,则返回  
        for(Field field : selfFields){  
            if(field.getName().equals(fieldName)){  
                return field;  
            }  
        }  

        //否则,查看父类中是否存在此字段,如果有则返回  
        Class<?> superClazz=clazz.getSuperclass();  
        if(superClazz!=null  &&  superClazz !=Object.class){  
            return getFieldByName(fieldName, superClazz);  
        }  

        //如果本类和父类都没有,则返回空  
        return null;  
    }  
}

ExcelException源代码,这个不重要,可自己实现

package core.exception;

@SuppressWarnings("serial")
public class ExcelException extends Exception {  

    public ExcelException() {  
        // TODO Auto-generated constructor stub  
    }  

    public ExcelException(String message) {  
        super(message);  
        // TODO Auto-generated constructor stub  
    }  

    public ExcelException(Throwable cause) {  
        super(cause);  
        // TODO Auto-generated constructor stub  
    }  

    public ExcelException(String message, Throwable cause) {  
        super(message, cause);  
        // TODO Auto-generated constructor stub  
    }  


}

使用示例:


    @RequestMapping(value = "/bankBranch/exportExcelPoi")
    @ResponseBody
    public void bankBranchExportExcel(BankBranch dto, HttpServletRequest request, HttpServletResponse response) {
LinkedHashMap<String,String> fieldMap = new LinkedHashMap<>();        
        List<BankBranch> bankBranchList = bankBranchMapper.selectExport(dto);

        fieldMap.put("branchName", "分行名称");
        fieldMap.put("branchDescType", "分行类型");
        fieldMap.put("bankName", "银行名称");
        fieldMap.put("stateName", "国家");
        fieldMap.put("provinceName", "省份");
        fieldMap.put("cityName", "城市");

        String fileName = "银行分行_"+new SimpleDateFormat("yyyyMMdd").format(new Date()).toString();
        try {
            ExcelPoiUtil.listToExcel(bankBranchList, fieldMap, response, fileName);
        } catch (Exception e) {
            e.printStackTrace();                
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_28830129/article/details/82119904
今日推荐