在數據倉庫中,ETL最基礎的步驟就是從數據源抽取所需的數據,這裏所說的數據源並不是僅僅是指數據庫,還包括excel、csv、xml等各類類型的數據接口文件,而這些文件中的數據不必定是結構化存儲的,好比各類各樣的報表文件,每每是一些複雜的表格結構,其中不只有咱們須要的數據,還有一些冗餘的、無價值的數據,這時咱們就沒法直接用通常數據加載工具直接讀取入庫了。也許你會想,數據源導出文件前先處理好數據就好了。然而,實際開發中數據源每每是多個的,並且涉及到不一樣的部門甚至公司,這其間不免會出現各類麻煩,甚至有些數據文件仍是純手工處理的,不必定能給到你滿意的數據格式。因此咱們不討論誰該負責轉換的問題,這裏主要介紹如何使用Apache POI
來從Excel數據文件中讀取咱們想要的數據,以及用Bean Validation
對數據內容按照預約的規則進行校驗。java
文章要點:git
Apache POI
是用Java編寫的免費開源的跨平臺的Java API,提供API給Java程式對Microsoft Office格式檔案進行讀和寫的操做。github
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>3.17</version> </dependency>
這裏須要注意的是Excel文檔的版本問題,Excel2003及之前版本的文檔使用HSSFWorkbook對象,Excel2007及以後版本使用HSSFWorkbook對象數據庫
// Excel2003及之前版本 Workbook workbook = new XSSFWorkbook(new FileInputStream(path)); // Excel2007及以後版本 Workbook workbook = new HSSFWorkbook(new FileInputStream(path));
Sheet是Excel文檔中的工做簿即表格頁面,讀取前要先找到數據所在頁面,能夠經過標籤名或者索引的方式獲取指定Sheet對象apache
// 按索引獲取 Sheet sheet = workbook.getSheetAt(index); // 按標籤名獲取 Sheet sheet = workbook.getSheet(label);
// 行索引row和列索引col都是以 0 起始 Cell cell = sheet.getRow(row).getCell(col);
獲取單元格的值以前首先要獲知單元格內容的類型,在Excel中單元格有6種類型:api
各類類型的內容還須要進一步判斷其數據格式,例如單元格的Type爲CELL_TYPE_NUMERIC時,它有多是Date類型,在Excel中的Date類型是以Double類型的數字存儲的,不一樣類型的值要調用cell對象相應的方法去獲取,具體狀況具體分析app
public Object getCellValue(Cell cell) { if(cell == null) { return null; } switch (cell.getCellType()) { case Cell.CELL_TYPE_STRING: return cell.getRichStringCellValue().getString(); case Cell.CELL_TYPE_NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { return cell.getDateCellValue(); } else { return cell.getNumericCellValue(); } case Cell.CELL_TYPE_BOOLEAN: return cell.getBooleanCellValue(); case Cell.CELL_TYPE_FORMULA: return formula.evaluate(cell).getNumberValue(); default: return null; } }
workbook.close();
當你要處理一個業務邏輯時,數據校驗是你不得不考慮和麪對的事情,程序必須經過某種手段來確保輸入進來的數據從語義上來說是正確的或者符合預約義的格式,一個Java程序通常是分層設計的,而不一樣的層多是不一樣的開發人員來完成,這樣就很容易出現不一樣的層重複進行數據驗證邏輯,致使代碼冗餘等問題。爲了不這樣的狀況發生,最好是將驗證邏輯與相應的模型進行綁定。工具
Bean Validation
規範的目標就是避免多層驗證的重複性,它提供了對 Java EE 和 Java SE 中的 Java Bean 進行驗證的方式。該規範主要使用註解的方式來實現對 Java Bean 的驗證功能,從而使驗證邏輯從業務代碼中分離出來。ui
Hibernate Validator
是 Bean Validation
規範的參考實現,咱們能夠用它來實現數據驗證邏輯,其Maven依賴以下:this
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>2.2.4</version> </dependency>
關於Bean Validation的詳細介紹可參考如下文章: JSR 303 - Bean Validation 介紹及最佳實踐 Bean Validation 技術規範特性概述
咱們要達到的效果是,模擬遊標
的方式構建一個Excel讀取工具類ExcelReadHelper
,而後加載Excel文件流來建立工具類實例,經過這個實例咱們能夠像遊標同樣設置當前的行和列,定好位置以後讀取出單元格的值並進行校驗,完成對Excel文件的讀取校驗操做。既然是讀取還有校驗數據,異常處理和提示固然是相當重要的,因此還要有人性化的異常處理方式,方便程序使用者發現Excel中格式或內容有誤的地方,具體到哪一行哪一項,出現的問題是什麼。
public class ExcelReadHelper { private static ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); //文件絕對路徑 private String excelUrl; private Workbook workbook; private Sheet sheet; //Sheet總數 private int sheetCount; //當前行 private Row row; private Validator validator; public ExcelReadHelper(File excelFile) throws ExcelException { validator = factory.getValidator(); excelUrl = excelFile.getAbsolutePath(); //判斷工做簿版本 String fileName = excelFile.getName(); String suffix = fileName.substring(fileName.lastIndexOf(".")); try { if(suffix.equals(".xlsx")) { workbook = new XSSFWorkbook(new FileInputStream(excelFile)); } else if(suffix.equals(".xls")) { workbook = new HSSFWorkbook(new FileInputStream(excelFile)); } else { throw new ExcelException("Malformed excel file"); } } catch(Exception e) { throw new ExcelException(excelUrl, e); } sheetCount = workbook.getNumberOfSheets(); } /** * 關閉工做簿 * @throws ExcelException * @throws IOException */ public void close() throws ExcelException { if (workbook != null) { try { workbook.close(); } catch (IOException e) { throw new ExcelException(excelUrl, e); } } } /** * 獲取單元格真實位置 * @param row 行索引 * @param col 列索引 * @return [行,列] */ public String getCellLoc(Integer row, Integer col) { return String.format("[%s,%s]", row + 1, CellReference.convertNumToColString(col)); } /** * 根據標籤設置Sheet * @param labels * @throws ExcelException */ public void setSheetByLabel(String... labels) throws ExcelException { Sheet sheet = null; for(String label : labels) { sheet = workbook.getSheet(label); if(sheet != null) { break; } } if(sheet == null) { StringBuilder sheetStr = new StringBuilder(); for (String label : labels) { sheetStr.append(label).append(","); } sheetStr.deleteCharAt(sheetStr.lastIndexOf(",")); throw new ExcelException(excelUrl, sheetStr.toString(), "Sheet does not exist"); } this.sheet = sheet; } /** * 根據索引設置Sheet * @param index * @throws ExcelException */ public void setSheetAt(Integer index) throws ExcelException { Sheet sheet = workbook.getSheetAt(index); if(sheet == null) { throw new ExcelException(excelUrl, index + "", "Sheet does not exist"); } this.sheet = sheet; } /** * 獲取單元格內容並轉爲String類型 * @param row 行索引 * @param col 列索引 * @return */ @SuppressWarnings("deprecation") public String getValueAt(Integer row, Integer col) { Cell cell = sheet.getRow(row).getCell(col); String value = null; if (cell != null) { switch (cell.getCellType()) { case Cell.CELL_TYPE_STRING: value = cell.getStringCellValue() + ""; break; case Cell.CELL_TYPE_NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { value = cell.getDateCellValue().getTime() + ""; } else { double num = cell.getNumericCellValue(); if(num % 1 == 0) { value = Double.valueOf(num).intValue() + ""; } else { value = num + ""; } } break; case Cell.CELL_TYPE_FORMULA: value = cell.getNumericCellValue() + ""; break; case Cell.CELL_TYPE_BOOLEAN: value = String.valueOf(cell.getBooleanCellValue()) + ""; break; } } return (value == null || value.isEmpty()) ? null : value.trim(); } /** * 獲取當前行指定列內容 * @param col 列索引 * @return */ public String getValue(Integer col) { return getValueAt(row.getRowNum(), col); } /** * 獲取Sheet名稱 * @return */ public String getSheetLabel() { String label = null; if(sheet != null) { label = sheet.getSheetName(); } return label; } /** * 行偏移 * @param offset 偏移量 * @return */ public Boolean offsetRow(Integer offset) { Boolean state = true; if(row == null) { row = sheet.getRow(offset-1); } else { row = sheet.getRow(row.getRowNum() + offset); if(row == null) { state = false; } } return state; } /** * 設置行 * @param index 索引 * @return */ public Boolean setRow(Integer index) { row = sheet.getRow(index); return row != null; } /** * 偏移一行 * @return */ public Boolean nextRow() { return offsetRow(1); } /** * 偏移到下一個Sheet * @return */ public Boolean nextSheet() { Boolean state = true; if(sheet == null) { sheet = workbook.getSheetAt(0); } else { int index = workbook.getSheetIndex(sheet) + 1; if(index >= sheetCount) { sheet = null; } else { sheet = workbook.getSheetAt(index); } if(sheet == null) { state = false; } } row = null; return state; } /** * 數據校驗 * @param obj 校驗對象 * @throws ExcelException */ public <T> void validate(T obj) throws ExcelException { Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj); if(constraintViolations.size() > 0) { Iterator<ConstraintViolation<T>> iterable = constraintViolations.iterator(); ConstraintViolation<T> cv = iterable.next(); throw new ExcelException(excelUrl, sheet.getSheetName(), row.getRowNum() + 1 + "", String.format("%s=%s:%s", cv.getPropertyPath(), cv.getInvalidValue(), cv.getMessage())); } } /** * 拋出當前Sheet指定行異常 * @param row 異常發生行索引 * @param message 異常信息 * @return */ public ExcelException excelRowException(Integer row, String message) { return new ExcelException(excelUrl, sheet.getSheetName(), row + 1 + "", message); } /** * 拋出當前行異常 * @param message 異常信息 * @return */ public ExcelException excelCurRowException(String message) { return new ExcelException(excelUrl, sheet.getSheetName(), row.getRowNum() + 1 + "", message); } /** * 拋出自定義異常 * @param message 異常信息 * @return */ public ExcelException excelException(String message) { return new ExcelException(excelUrl, message); } }
public class ExcelException extends Exception { public ExcelException() { super(); } public ExcelException(String message) { super(message); } public ExcelException(String url, String message) { super(String.format("EXCEL[%s]:%s", url, message)); } public ExcelException(String url, String sheet, String message) { super(String.format("EXCEL[%s],SHEET[%s]:%s", url, sheet, message)); } public ExcelException(String url, String sheet, String row, String message) { super(String.format("EXCEL[%s],SHEET[%s],ROW[%s]:%s", url, sheet, row, message)); } public ExcelException(String url, Throwable cause) { super(String.format("EXCEL[%s]", url), cause); } }
// 使用Excel文件對象初始化ExcelReadHelper ExcelReadHelper excel = new ExcelReadHelper(file); // 第一頁 excel.setSheetAt(0); // 「Sheet1」頁 excel.setSheetByLabel("Sheet1"); // 下一頁 excel.nextSheet(); // 第一行(以 0 起始) excel.setRow(0); // 下一行 excel.nextRow(); // 偏移兩行 excel.offsetRow(2); // 當前行第一列的值 String value1 = excel.getValue(0); // 第一行第一列的值 String value2 = excel.getValueAt(0,0); // 獲取單元格真實位置(如索引都爲0時結果爲[1,A]) String location = excel.getCellLoc(0,0); // 當前頁標題(如「Sheet1」) String label = excel.getSheetLabel(); // 校驗讀取的數據 try { excel.validate(obj); } catch (ExcelException e) { // 錯誤信息中包含具體錯誤位置以及緣由 e.printStackTrace(); } //拋出異常,結果自動包含出現異常的Excel路徑 throw excel.excelException(message); //拋出指定行異常,結果自動包含出現錯誤的Excel路徑、當前頁位置 throw excel.excelRowException(0, message); //拋出當前行異常,結果自動包含出現錯誤的Excel路徑、當前頁、當前行位置 throw excel.excelCurRowException(message); //關閉工做簿Workbook對象 excel.close();
本文爲做者kMacro原創,轉載請註明來源:https://zkhdev.github.io/2018/10/14/java-dev6/