poi幾多愁,恰似源碼的溫柔

導讀

最近,公司在作導入導出的項目,首先想到的是poi的導入和導出。若是每次導入和導出都要重寫的話,那麼,實在是浪費時間和精力。因而,封裝了原生的poi的導入和導出。在封裝的時候,就會出現一系列的問題。java

在進行導入和導出的時候,咱們必需要熟悉Excel,尤爲是它所支持的類型,如圖所示:數據庫

excel的數據類型

由於我用的是java,這要和java語言相匹配。java有八大基本類型(包裝類型)、字符串類型、日期類型、時間戳類型等。Excel類型要和這些類型相匹配,不然,就會出現導入和導出的問題。json

導出


導出的思想

  1. 採用hibernate或mybatis框架,從數據庫中取出數據,假設名字爲originalList
  2. 使用appach下面的beanutils框架的PropertyUtils.getProperty((Object bean, String name))方法過濾數據。它是經過對象的屬性名稱拿到對象的屬性值。這裏面用到的反射。
  3. 將過濾後的數據放到JsonObject集合中,key是當前對象的屬性名,value是屬性對應的屬性值。假設名爲dataList。
  4. 使用LinkedHashMap封裝Excel表的表頭,也就是上圖中的第一行數據。key值是上述對象的數值名,value值表頭的中文名。假設名爲headerMap。爲何使用LinkedHashMap,而不是HashMap?這個在下文說
  5. 實例化poi導出的各個對象,便於操做。
  6. 遍歷headerMap,獲取當前的key值。
  7. 遍歷dataList,若是list中的當前對象是JsonObject對象,直接獲取與key值相同的屬性值。若是不是JsonObject對象,能夠經過PropertyUtils.getProperty((Object bean, String name))獲取值。
  8. 拿到值後,建立當前單元格,把數據填充進去。
  9. 響應客戶端的導出請求

  • 解決「爲何使用LinkedHashMap,而不是HashMap?」這個問題

一、 由於HashMap的key值是對象的hashCode值,hashCode值是散列值。這樣存儲方式是散列存儲,就像浴缸中的魚同樣,它的位置是不肯定的,於是,輸出結果具備不少不肯定性。也就是說,輸出結果的順序和咱們存儲的順序不一致,如圖所示:mybatis

HashMap輸出結果

二、可是,LinkedHashMap採用的是鏈表,鏈表節點的存儲的是下一個引用對象的首地址。就像是一根繩子上節點,系在哪兒就在哪那兒,位置不會改變的,如圖所示:app

LinkedHashMap輸出結果.png

上述思想的方法

/**
     * Created By zby on 18:22 2019/3/9
     *
     * @param response  響應客戶端的導出請求
     * @param headerMap 建立表頭
     * @param dataList  數據集
     * @param excelName excel表名
     */
    public static void exportSimpleExcel(HttpServletResponse response, LinkedHashMap<String, String> headerMap, List<JSONObject> dataList, String excelName) {
        // 建立個workbook
        XSSFWorkbook workbook = new XSSFWorkbook();
        // 建立一個sheet
        XSSFSheet sheet = workbook.createSheet();
        Pattern chinese_pattern = Pattern.compile("[\\u4e00-\\u9fa5]");
        if (sheet != null) {
            try {
                // 寫數據
                Integer i = 0;
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    Row headRow = sheet.getRow(0);
                    if (isNull(headRow)) {
                        headRow = sheet.createRow(0);
                    }
                    CellStyle cellStyle = workbook.createCellStyle();
                    cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN); //下邊框
                    cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);//左邊框
                    cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);//上邊框
                    cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);//右邊框
                    cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);//居中
                    cellStyle.setFillBackgroundColor(HSSFColor.GREY_40_PERCENT.index);
                    
                    //建立標題行,若是第一遍已經建立了,就直接根據下標獲取行,
                    // 若是沒有建立標題行,就根據當前下標建立當前行,如下相同。
                    Cell headCell = headRow.createCell(i);
                    headCell.setCellValue(value);
                    headCell.setCellStyle(cellStyle);
                    
                    //設置標題下的數據,每建立標題的一個單元格,就建立該單元格下的全部列
                    //行數就是一行標題單元格+數據單元格的個數,即merginRow+j
                    for (int j = 0; j < dataList.size(); j++) {
                        Object obj = dataList.get(j);
                        //須要判斷json是不是jsonObject的實例化對象,別人在調用這個方法時,
                        // 咱們不清楚jsons集合中是否存在其餘類的對象,由於須要做個驗證
                        Object val = null;
                        if (obj instanceof JSONObject) {
                            JSONObject json = (JSONObject) obj;
                            val = json.get(key);
                        } 
//                    else {這裏若是不是JsonObject對象,就用這種方式獲取
//                            try {
//                                //這是appach下的方法,其經過屬性名稱,反射獲得當前對象屬性的數值
//                                //json當中的key就至關於其屬性,其值就是value值
//                                val = getProperty(obj, key);
//                            } catch (Exception e) {
//                                if (obj != null) {
//                                    LogUtil.warn(logger, "類: " + obj.getClass() + ",  屬性: " + key);
//                                }
//                                val = null;
//                            }
//                        }

                        Row row = sheet.getRow(1 + j);
                        if (isNull(row)) {
                            row = sheet.createRow(1 + j);
                        }
                        Cell cell = row.createCell(i);
                        cell.setCellStyle(cellStyle);
                        if (val instanceof Double) {
                            cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                            cell.setCellValue(((Double) val).doubleValue());
                        } else if (val instanceof Integer) {
                            cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                            cell.setCellValue(((Integer) val).intValue());
                        } else if (val instanceof Date) {
                            cell.setCellValue(ISO_DATETIME_FORMAT.format(val));
                        } else if (val instanceof Calendar) {
                            cell.setCellValue((Calendar) val);
                        } else if (val instanceof Boolean) {
                            cell.setCellType(Cell.CELL_TYPE_BOOLEAN);
                            cell.setCellValue(((Boolean) val).booleanValue());
                        } else if (val instanceof String || val instanceof RichTextString) {
                            cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                            String cellValue = (String) val;
                            int width = cellValue.length();
                            Matcher matcher = chinese_pattern.matcher(cellValue);
                            while (matcher.find()) {
                                width++;
                            }
                            width = (width > 6) ? ((width < 80) ? width : 80) : 6;
                            sheet.setColumnWidth(i, (256 * width + 184) + 500);
                            cell.setCellValue(cellValue);
                        } else {
                            cell.setCellValue("");
                        }
                    }
                    i++;
                }
                String realPath = (getSispPath() + "uploadExcelRecord");
                // 新的文件名
                String newFileName = excelName + DateUtil.ISO_DATETIME_FORMAT_NONE.format(new Date()) + ".xlsx";
                // 判斷路徑是否存在
                File dir = new File(realPath);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                // 寫入到新的excel
                File newFile = new File(realPath, newFileName);
                FileOutputStream fos = new FileOutputStream(newFile);
                workbook.write(fos);
                fos.flush();
                fos.close();
                downloadExcel(response, newFile, excelName);
                deleteFile(newFile);
            } catch (Exception e) {
                logger.error("——————————————————————————數據寫入表格失敗————————————————————————————");
            }
        }
    }

注意事項

cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(((Double) val).doubleValue());

針對這兩句,咱們查看poi的cell.setCellValue()方法的底層是如何實現的。框架

void setCellValue(double var1);

 void setCellValue(Date var1);

 void setCellValue(Calendar var1);

 void setCellValue(RichTextString var1);

 void setCellValue(String var1);

 void setCellValue(boolean var1);

在java的八大類型當中,poi支持浮點型的double類型,整型的integer類型。爲何是這兩種類型?這兩種格式是java的默認類型。double能夠轉整型的。
全部,咱們在進行數據導出時,必定要主營待導出對象的數據類型,避免數據沒法導出。spa

導入


導入的思想

  1. 在導入時,接收到客戶端發出的導入請求。
  2. 設置導入數據的開始行和結束行,開始行默認是0,結束行調用者給出的參數
  3. 設計導入的開始列和結束列,開始列通常是0,結束列通常是調用給出的列
  4. 獲取導入的文件,判斷Excel的版本。

導入的方法

/**
     * Created By zby on 19:24 2019/3/9
     *
     * @param request           客戶端發出的導入請求
     * @param sheetDataStartRow 導入的開始行
     * @param sheetDataEndCol   導入的結束列
     */
    public static LinkedHashMap<String, List<JSONObject>> importMultiSheetExcel(HttpServletRequest request, Integer sheetDataStartRow, String sheetDataEndCol) {
        //建立導入和行和列
        LinkedHashMap<Integer, String> sheetDataEndColMap = new LinkedHashMap<>();
        LinkedHashMap<Integer, Integer> sheetFirstDataRowMap = new LinkedHashMap();
        sheetDataEndColMap.put(0, sheetDataEndCol);
        sheetFirstDataRowMap.put(0, sheetDataStartRow);
        LinkedHashMap<String, List<JSONObject>> resMap = new LinkedHashMap<>();
        try {
            MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
            ifNullThrow(multipartRequest, ResultCodeEnum.ILLEGAL_PARAM);
            MultipartFile file = multipartRequest.getFile("file");
            Workbook work = getWorkbook(file.getInputStream(), file.getOriginalFilename());
            ifNullThrow(work, ResultCodeEnum.ILLEGAL_PARAM);
            Sheet sheet = null;
            Row row = null;
            Cell cell = null;
            //遍歷Excel中全部的sheet
            for (int i = 0; i < work.getNumberOfSheets(); i++) {
                sheet = work.getSheetAt(i);
                if (null == sheet) {
                    continue;
                }
                String sheetDataEndCo = sheetDataEndColMap.get(i);
                Integer sheetFirstDataRow = sheetFirstDataRowMap.get(i);
                sheetDataEndCo = isNotBlank(sheetDataEndCo) ? sheetDataEndCo.toUpperCase() : null;
                sheetFirstDataRow = isNotNull(sheetFirstDataRow) ? sheetFirstDataRow : 1;
                List<JSONObject> list = new ArrayList<>();
                //遍歷當前sheet中的全部行
                for (int j = 0; j <= sheet.getLastRowNum(); j++) {
                    row = sheet.getRow(j);
                    if (row == null) {
                        continue;
                    }
                    if (j >= sheetFirstDataRow.intValue()) {
                        JSONObject json = new JSONObject();
                        //導入限制最大列索引數爲200,正常根據sheetDataEndCol肯定,最大列索引數限制只用來防止傳入錯誤
                        for (int k = 0; k < 200; k++) {
                            String colName = CellReference.convertNumToColString(k);
                            cell = row.getCell(k);
                            if (isNotNull(cell)) {
                                if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
                                    cell.setCellType(Cell.CELL_TYPE_STRING);
                                    json.put(colName, cell.getStringCellValue().trim());
                                } else if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
                                    if (HSSFDateUtil.isCellDateFormatted(cell)) {
                                        json.put(colName, cell.getDateCellValue());
                                    } else {
                                        cell.setCellType(Cell.CELL_TYPE_STRING);
                                        json.put(colName, cell.getStringCellValue().trim());
                                    }
                                }
                            } else {
                                json.put(colName, null);
                            }
                            if (colName.equals(sheetDataEndCo)) {
                                break;
                            }
                        }
                        list.add(json);
                    }
                }
                resMap.put("sheet" + i, list);
            }
            return resMap;
        } catch (Exception e) {
            throw new GeneralBizException("Excel導入異常:" + e.getMessage());
        }
    }

注意事項

由於Excel格式的時間和java的時間不一致,咱們在導入時須要注意Excel的格式。首先判斷當前單元格的數據是否是數字型的,若是是數字型的,在判斷是否是日期類型的,若是是日期類型,再轉爲日期類型。不然,所有是字符型的數據。hibernate

if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
    cell.setCellType(Cell.CELL_TYPE_STRING);
    json.put(colName, cell.getStringCellValue().trim());
} else if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
    if(HSSFDateUtil.isCellDateFormatted(cell)){
        json.put(colName, cell.getDateCellValue());
    }else {
        cell.setCellType(Cell.CELL_TYPE_STRING);
        json.put(colName, cell.getStringCellValue().trim());
    }
}

這裏爲何不作數值型的判斷,由於,字符串能夠轉爲各中數值型的數據。設計

結束語

任何框架的搭建,都須要紮實的基礎,只有基礎足夠強大,再加上靈活的設計思想,就可以解決不少事情。excel

相關文章
相關標籤/搜索