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();                
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章