官方Github地址:https://github.com/alibaba/easyexcel
官方使用說明:https://alibaba-easyexcel.github.io/index.html
使用步驟:
- 在頁面上新增導入按鈕和文件選擇框,代碼參考:
<a id="btnImport" class="easyui-linkbutton" data-options="iconCls:'icon-save'" onclick="selectFile()" style="float: right;">導入</a>
<input id="filebox" name="filebox" type="file" onchange="uploadFile()" style="float:right;display:none;" />
- 加入JavaScript方法調用后台,代碼參考:
1 function selectFile(){ 2 $("#filebox").click(); 3 } 4 function uploadFile() { 5 if(isLowerIE10()){ 6 $.messager.alert('錯誤', "當前瀏覽器版本太低,請使用IE10及以上或者其他瀏覽器",'error'); 7 } else { 8 var file = $("#filebox").val(); 9 if (file.endWith(".xlsx")) { 10 var options = { 11 type : 'post', 12 url : "upload.do", 13 dataType : 'json', 14 complete : function(result) { 15 $('#dg').datagrid("loaded"); 16 var result = result.responseJSON; 17 $.messager.alert('操作提示', result.msg,'info',function(){ 18 $('#dg').datagrid('reload'); 19 }); 20 } 21 } 22 $('#dg').datagrid("loading"); 23 $("#searchForm").ajaxSubmit(options); 24 } else { 25 $.messager.alert('錯誤', "請選擇Excel2007以上版本文件,擴展名:xlsx",'error'); 26 } 27 } 28 }
- 后台控制器Controller中接受文件流並傳給業務層做進一步處理,代碼參考
@RequestMapping("upload.do") @ResponseBody public Result upload(HttpServletRequest req, @RequestParam(value = "filebox") MultipartFile file) { Result result = Result.getInstanceError(); try { result = prodlineBugService.importByExcel(file); } catch (Exception e) { log.error("產線不良Excel導入異常:", e); result.setMsg(e.getMessage()); } return result; }
- 業務層service
public Result importByExcel(MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), ProdlineBugVO.class, new ProdlineBugListener(this)).sheet().autoTrim(true) .doRead(); return Result.getInstanceSuccess(); }
- 業務對象VO用注解標識一下列名。
1 @ExcelIgnoreUnannotated 2 public class ProdlineBugVO { 3 4 /** 5 * ID 6 */ 7 @ExcelProperty(value = "ID") 8 private Long id; 9 /** 10 * 期間(yyyy-mm) 11 */ 12 private String yearMonth; 13 /** 14 * 供應商編碼 15 */ 16 @ExcelProperty(value = "供應商編碼") 17 private String supplierCode; 18 /** 19 * 物料編碼 20 */ 21 @ExcelProperty(value = "物料編碼") 22 private String itemNumber; 23 /** 24 * 包含Y 不包含N 25 */ 26 @ExcelProperty(value = "不良數") 27 private BigDecimal bugNum; 28 /** 29 * 總數 30 */ 31 private BigDecimal totalNum; 32 /** 33 * 包含Y 不包含N 34 */ 35 @ExcelProperty(value = "工單號") 36 private String icmoCode; 37 /** 38 * 描述 39 */ 40 @ExcelProperty(value = "描述") 41 private String description; 42 /** 43 * 季度考核ID 44 */ 45 private Long quarterExamineId; 46 /** 47 * 發生時間 48 */ 49 @ExcelProperty(value = "發生日期") 50 private Date actualTime; 51 52 private String itemName; 53 private String supplierName; 54 /** excel行號 */ 55 private Integer rowIndex; 56 57 /** 58 * 設置 ID 59 */ 60 public Long getId() { 61 return id; 62 } 63 64 /** 65 * 獲取 ID 66 */ 67 public void setId(Long id) { 68 this.id = id; 69 } 70 71 /** 72 * 設置 期間(yyyy-mm) 73 */ 74 public String getYearMonth() { 75 return yearMonth; 76 } 77 78 /** 79 * 獲取 期間(yyyy-mm) 80 */ 81 public void setYearMonth(String yearMonth) { 82 this.yearMonth = yearMonth; 83 } 84 85 /** 86 * 設置 供應商編碼 87 */ 88 public String getSupplierCode() { 89 return supplierCode; 90 } 91 92 /** 93 * 獲取 供應商編碼 94 */ 95 public void setSupplierCode(String supplierCode) { 96 this.supplierCode = supplierCode; 97 } 98 99 /** 100 * 設置 物料編碼 101 */ 102 public String getItemNumber() { 103 return itemNumber; 104 } 105 106 /** 107 * 獲取 物料編碼 108 */ 109 public void setItemNumber(String itemNumber) { 110 this.itemNumber = itemNumber; 111 } 112 113 public BigDecimal getBugNum() { 114 return bugNum; 115 } 116 117 public void setBugNum(BigDecimal bugNum) { 118 this.bugNum = bugNum; 119 } 120 121 public BigDecimal getTotalNum() { 122 return totalNum; 123 } 124 125 public void setTotalNum(BigDecimal totalNum) { 126 this.totalNum = totalNum; 127 } 128 129 /** 130 * 設置 包含Y 不包含N 131 */ 132 public String getIcmoCode() { 133 return icmoCode; 134 } 135 136 /** 137 * 獲取 包含Y 不包含N 138 */ 139 public void setIcmoCode(String icmoCode) { 140 this.icmoCode = icmoCode; 141 } 142 143 /** 144 * 設置 描述 145 */ 146 public String getDescription() { 147 return description; 148 } 149 150 /** 151 * 獲取 描述 152 */ 153 public void setDescription(String description) { 154 this.description = description; 155 } 156 157 /** 158 * 設置 季度考核ID 159 */ 160 public Long getQuarterExamineId() { 161 return quarterExamineId; 162 } 163 164 /** 165 * 獲取 季度考核ID 166 */ 167 public void setQuarterExamineId(Long quarterExamineId) { 168 this.quarterExamineId = quarterExamineId; 169 } 170 171 /** 172 * 設置 發生時間 173 */ 174 public Date getActualTime() { 175 return actualTime; 176 } 177 178 /** 179 * 獲取 發生時間 180 */ 181 public void setActualTime(Date actualTime) { 182 this.actualTime = actualTime; 183 } 184 185 /** 186 * 創建人(郵箱) 187 */ 188 private String createBy; 189 /** 190 * 創建時間 191 */ 192 private Date createTime; 193 /** 194 * 更新人(郵箱) 195 */ 196 private String updateBy; 197 /** 198 * 更新時間 199 */ 200 private Date updateTime; 201 202 public String getCreateBy() { 203 return createBy; 204 } 205 206 public void setCreateBy(String createBy) { 207 this.createBy = createBy; 208 } 209 210 public Date getCreateTime() { 211 return createTime; 212 } 213 214 public void setCreateTime(Date createTime) { 215 this.createTime = createTime; 216 } 217 218 public String getUpdateBy() { 219 return updateBy; 220 } 221 222 public void setUpdateBy(String updateBy) { 223 this.updateBy = updateBy; 224 } 225 226 public Date getUpdateTime() { 227 return updateTime; 228 } 229 230 public void setUpdateTime(Date updateTime) { 231 this.updateTime = updateTime; 232 } 233 234 public String getItemName() { 235 return itemName; 236 } 237 238 public void setItemName(String itemName) { 239 this.itemName = itemName; 240 } 241 242 public String getSupplierName() { 243 return supplierName; 244 } 245 246 public void setSupplierName(String supplierName) { 247 this.supplierName = supplierName; 248 } 249 250 public Integer getRowIndex() { 251 return rowIndex; 252 } 253 254 public void setRowIndex(Integer rowIndex) { 255 this.rowIndex = rowIndex; 256 } 257 }
- 定義好Listener來調用保存方法。
1 public class ProdlineBugListener extends AnalysisEventListener<ProdlineBugVO> { 2 private Logger log = LoggerFactory.getLogger(this.getClass()); 3 4 /** 一次處理的數量,防止一次處理的過多造成oom */ 5 private static final int BATCH_COUNT = 5000; 6 7 List<ProdlineBugVO> list = new ArrayList<ProdlineBugVO>(); 8 9 private ProdlineBugService prodlineBugService; 10 11 public ProdlineBugListener() { 12 } 13 14 public ProdlineBugListener(ProdlineBugService prodlineBugService) { 15 this.prodlineBugService = prodlineBugService; 16 } 17 18 @Override 19 public void invoke(ProdlineBugVO data, AnalysisContext context) { 20 data.setRowIndex(context.readRowHolder().getRowIndex() + 1); 21 list.add(data); 22 if (list.size() >= BATCH_COUNT) { 23 // 達到BATCH_COUNT再處理,防止內存中存儲過大數據,容易OOM 24 prodlineBugService.batchSave(list); 25 list.clear(); 26 } 27 } 28 29 @Override 30 public void doAfterAllAnalysed(AnalysisContext arg0) { 31 // 這里也要保存數據,確保最后遺留的和不到batch_count的數據也存儲到數據庫 32 prodlineBugService.batchSave(list); 33 } 34 }
注意事項
- 事務問題:如果不想Excel處理過程中被分成好多比事務,那請將讀Excel的方法寫到有事務控制的業務層。這樣,當某一行內容校驗失敗,拋出異常,前面的操作就會回滾。
- 對於日期格式,可以用Date來接收。框架默認已經支持很多常用的數字格式,例如:"yyyy-MM-dd"等,但如果沒有解析成功,那就需要使用@DateTimeFormat("yyyy年MM月dd日")注解配置
- @ExcelProperty注解支持按列名或列索引來配置表頭,如果使用列名的方式,那即使列順序變更也不會影響讀數據。但千萬不要2種方式同時配置。
- 對於少數Excel單元格內容格式不確定的情況(比如動態內容導入)。可能無法定義明確的VO值對象,可以使用Map<Integer, Object>來接受行內容。key是列索引。
- 如果單元格內容為空,map中不會有這個單元格對應的Entry,所以不要用EntrySet來循環
- 可以一開始就記錄好表頭的列數量,然后用這個列數量來循環Map,這樣就知道哪一列是空的了。
- 如果Excel中有空行,框架會自動跳過不會調用invoke方法。
- autoTrim如果是true,讀單元格內容時會自動trim。
- 框架默認第一行是表頭,數據是從第二行開始的。如果不是可以使用headRowNumber方法指定。
- 該框架已經解決了POI頭疼的性能問題而且簡化了很多Excel讀寫的代碼。更多功能可以參考官方github