首先先簡單介紹一下本文的代碼,主要是通過前后台的交互,讀取excel中的數據存入到數據庫中。(另有關於sheet為空,sheet中某行某列數據為空跟數據庫中對應字段不能為空而導致的不能存入的判斷等方法的思路)。
1. 本文針對的是1個或多個sheet的情況
2. 默認每個sheet第一行存放的是對應數據庫的字段名稱。比如:name,age等,這樣寫的原因是比較靈活,不同的sheet可以存不同的字段值,具體參考下面的圖。
excel格式如圖:
代碼部分:
首先要在pom.xml中添加poi的依賴
1 <!-- org.apache.poi POI依賴包 --> 2 <dependency> 3 <groupId>org.apache.poi</groupId> 4 <artifactId>poi</artifactId> 5 <version>3.15</version> 6 </dependency> 7 <dependency> 8 <groupId>org.apache.poi</groupId> 9 <artifactId>poi-ooxml-schemas</artifactId> 10 <version>3.15</version> 11 </dependency> 12 <dependency> 13 <groupId>org.apache.poi</groupId> 14 <artifactId>poi-ooxml</artifactId> 15 <version>3.15</version> 16 </dependency>
前台form:
要注意的是:
1. input的type類型要是file。
2. input的name屬性要跟后台接收的requestparam參數一致。
3. form的id后面js代碼會用到。
4. 其他的屬性可以根據情況自行調整。
1 <form class="form-horizontal" role="form" id="controlform"> 2 <input type="file" style="opacity:0;width:100%;height:100%;position:absolute;top:0;left:0" id="importFile" name = "file" onchange="upload()" class="btn btn-primary query"/> 3 </form>
前台button:
要注意的是:
1. 按鈕的id和onclick事件后面會用到。
2. 其他的屬性根據情況自行調整。
1 <button type="button" class="btn btn-primary query" id="btnSearch1" onclick="importFile()"> 2 <span class="glyphicon glyphicon-search" aria-hidden="true" th:text="#{searchbutton}"></span> 3 </button>
前台js:
function upload() { //document.getElementById('importFile').files[0].name;//可以獲取文件名稱 var formData = new FormData($("#controlform")[0]); $.ajax({ type:'POST', url:'/customsupplement/reportList/uploadExcel?formid=12131232131',//參數可以根據情況傳遞 data:formData, async: false, cache: false, contentType: false, processData: false, //必須要 success: function (data) { $("#importFile").val(""); if (data.valid == "true") { alert("導入成功"); }else{ alert(data.valid);//彈出的就是報錯的信息 } }, error:function(data){ alert("系統錯誤"); } }); } function importFile() { $("#importFile").click(); }
后台代碼(無檢查部分):
1 @RequestMapping(method = RequestMethod.POST, value = "/uploadExcel") 2 @ResponseBody 3 public Map<String, String> uploadExcel(@RequestParam(value = "file", required = false) MultipartFile inputFile, String formid) throws IOException {//其中value=file的file要與form中input標簽里的name屬性的值一致,formid為傳過來的參數,可有可無, 4 //用於存放所有不正確的信息 如果是 "" 說明沒有報錯可以執行sql 5 String msg = ""; 6 //用於返回前台的標志 7 String valid = ""; 8 //用於存放將要執行的所有sql 9 List sqlList = new ArrayList(); 10 //用於返回前台標志的map 11 Map mp = new HashMap(); 12 if (inputFile.isEmpty()) { 13 msg += "此excel文件為空"; 14 } 15 if("".equals(msg)){ 16 //通過formid查詢操作的表名稱,這里可以根據業務自行做 17 String tablename = reportListService.getTableName(formid); 18 //獲得Workbook工作薄對象 19 Workbook workbook = null; 20 //獲得文件名 21 String fileName = inputFile.getOriginalFilename(); 22 //獲取excel文件的io流 23 InputStream is = inputFile.getInputStream(); 24 //根據文件后綴名不同(xls和xlsx)獲得不同的Workbook實現類對象 25 if(fileName.endsWith("xls")){ 26 workbook = new HSSFWorkbook(is);//2003 27 }else if(fileName.endsWith("xlsx")){ 28 workbook = new XSSFWorkbook(is);//2007 及2007以上 29 } 30 //獲取sheet的數量 31 int sheetnum = workbook.getNumberOfSheets(); 32 //存在sheet並且沒有報錯信息 33 if (sheetnum > 0 && "".equals(msg)) { 34 //將要執行的sql拼接好並放入list中 35 sqlList = dealsheet(workbook,sheetnum,tablename); 36 } 37 //list中有要執行的sql數據 38 if (sqlList.size() > 0) { 39 //批量插入 40 valid = reportListService.insertSqlList(sqlList); 41 } 42 } 43 44 if (valid == "true") { 45 mp.put("valid", "true"); 46 } else { 47 mp.put("valid", msg); 48 } 49 return mp; 50 }
dealsheet方法:
1 public static List dealsheet(Workbook workbook,int sheetnum,String tablename){ 2 //用於存放sql集合 3 List sqlList = new ArrayList(); 4 for(int s=0;s<sheetnum;s++){ 5 //用於判斷是否有id列 6 String idSign = "false"; 7 //獲取要解析的表格(第一個sheet) 8 HSSFSheet sheet = (HSSFSheet) workbook.getSheetAt(s); 9 //獲取總行數(包括列頭) 10 int arrays = sheet.getPhysicalNumberOfRows(); 11 //行數為0,說明此sheet沒有數據,直接忽略掉。 12 if(arrays == 0){ 13 continue; 14 } 15 //用於存儲insertsql的前半部分(取sheet的第一行) 16 String sql = ""; 17 //用於存儲insertsql的后半部分 18 String sqltemp = ""; 19 //獲取總列數 20 int rows = sheet.getRow(0).getPhysicalNumberOfCells(); 21 if(arrays>0){//拼接insert sql的前半部分(列頭) 22 HSSFRow row = sheet.getRow(0);//獲得要解析的行(第一行列頭部分) 23 for(int i=0;i<rows;i++){ 24 Cell cell = row.getCell(i); 25 cell.setCellType(CellType.STRING);//讀取數據前設置單元格類型 如果不設置有可能會報錯 26 sql += cell.getStringCellValue()+","; 27 if("ID".equals(cell.getStringCellValue().toUpperCase())){ 28 idSign = "true";//sheet中有id列 29 } 30 } 31 //sheet中有id列 32 if("true".equals(idSign)){ 33 sql = "insert into "+tablename+" ("+sql.substring(0,sql.length()-1)+") values "; 34 }else{ 35 sql = "insert into "+tablename+" (id,"+sql.substring(0,sql.length()-1)+") values "; 36 } 37 } 38 //拼接insert sql的后半部分(值) 39 for (int k = 1; k<arrays; k++) { 40 //獲得要解析的行 41 HSSFRow row = sheet.getRow(k); 42 for(int f=0;f<rows;f++){ 43 Cell cell = row.getCell(f); 44 if(cell != null){ 45 if(cell.getCellType() == 0 && HSSFDateUtil.isCellDateFormatted(cell)){//判斷是否是時間類型的 46 String datevalue = DateFormatUtils.format(cell.getDateCellValue(), "yyyy-MM-dd HH:mm:ss"); 47 sqltemp += datevalue+","; 48 }else{ 49 cell.setCellType(CellType.STRING);//讀取數據前設置單元格類型 如果不設置有可能會報錯 50 sqltemp += cell.getStringCellValue()+","; 51 } 52 }else{ 53 sqltemp += ","; 54 } 55 } 56 //sheet中有id列 57 if("true".equals(idSign)){ 58 sqlList.add(sql+"('"+sqltemp.substring(0,sqltemp.length()-1).replaceAll(",","','")+"')"); 59 }else{ 60 sqlList.add(sql+"(UUID(),'"+sqltemp.substring(0,sqltemp.length()-1).replaceAll(",","','")+"')"); 61 } 62 sqltemp = ""; 63 } 64 } 65 return sqlList; 66 }
檢查的內容:
1. 查出為空的sheet,自動忽略。
2. 某行某列對應的字段如果為空是否會影響到insert sql的執行。
3. excel中每個sheet第一行存放的字段名稱是否真實存在於數據庫對應表中。
4. excel中是否有id列,如果沒有就在后台自動添加一個(這個可以根據情況自行決定)。
驗證部分:
首先mysql中有一個語句可以直接查出某個表的對應字段名稱,長度,是否允許為空的信息,那么我們就可以直接拿過來用了,因為上面的代碼中我們已經寫了如何去遍歷sheet中的每個單元格的內容,那么同理,我們可以在遍歷的同時加上下面的這些驗證,也可以單獨寫一個方法去在拼接sql之前就檢驗一下excel中的數據是否符合要求。同時將不符合的信息放入msg中,一起返回給前台即可。
1 Select COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE from INFORMATION_SCHEMA.COLUMNS Where table_name = '表名' AND table_schema = '數據庫名'
注意:
1. column_name是字段名稱。(用來驗證sheet中第一行的字段值是否真實存在於數據庫中)
2. is_nullable意思是是否可以為空。(可以驗證sheet中某行某列的值如果為空是否可以存入數據庫)
3. colunm_type意思是字段類型和長度。(可以取出varchar類型的長度,並跟sheet中的做對比)
下面補充一個判斷某個字符串長度的方法(漢字2個字符,其他一個字符的標准)
1 public static int String_length(String value) { 2 int valueLength = 0; 3 String chinese = "[\u4e00-\u9fa5]"; 4 for (int i = 0; i < value.length(); i++) { 5 String temp = value.substring(i, i + 1); 6 if (temp.matches(chinese)) { 7 valueLength += 2; 8 } else { 9 valueLength += 1; 10 } 11 } 12 return valueLength; 13 }
最后,還要提醒一下,在excel中如果存儲時間類型的數據,格式可能會不對,這個時候需要我們設置一下excel這一列的格式。舉個例子:比如我們想要將2019/10/22 11:45:47存入某個單元格中,如果直接存就會如下圖一樣,excel會自動去掉秒的部分。
解決辦法如下:在單元格/列/行上右鍵--設置單元格格式--數字--自定義,之后如圖所示填寫保存 yyyy-m-d h:mm:ss,那么保存之后的就是年月日時分秒格式的了。
持續更新!!