官方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