[TOC] 复杂EasyExcel的导入导出。使用方案:Alibaba EasyExcel。 > 官网地址:https://www.yuque.com/easyexcel/doc/easyexcel ## 优点分析 - 可以实现自动按名称匹配Excel表头,即使出现了表头顺序变化; - 表头与实体类中注解不匹配时,不会报错,较为友好,只是不能匹配的数据会返回null。 步骤解释: 1. 创建接收数据的model或者pojo; 2. 定义一个listener用来监听解析完成的数据,并完成数据入库; ## 使用Demo 前提:引用对应的maven。 ``` <easyexcel.version>2.2.6</easyexcel.version> ``` ``` <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>${easyexcel.version}</version> </dependency> ``` ### 1-定义数据接收对象 ``` import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.mrzihan.resc.util.excel.CustomStringIntegerConverter; import lombok.Data; /** * 对应模板:2020年省目录所有字段导出,共29列 * Description:资源导入映射实体对象 * Author:mrzihan * CreateDate:2018/12/6 * "@ExcelProperty"使用注意事项: * - 属性名首字母不能大写,否则取不到值 */ @Data public class ResImportByNamePojo { @ExcelProperty("区域") private String areaName; @ExcelProperty("数源单位") private String supplyDept; @ExcelIgnore private String supplyDeptId; @ExcelProperty("信息资源名称") private String resTitle; @ExcelProperty("归集表英文名称") private String tableName; @ExcelIgnore private String resKeyword; @ExcelProperty("信息资源摘要") private String abstractInfo; @ExcelProperty("所属系统名称") private String sysName; @ExcelProperty("信息资源格式") private String resFormat; @ExcelProperty("重点领域分类") private String fieldName; @ExcelProperty("更新频率") private String updateCycle; @ExcelProperty("修改日期") private String CreateDate; @ExcelProperty("是否引用省级") private String extended; @ExcelProperty("状态") private String sts; @ExcelProperty("归集状态") private String collectSts; @ExcelIgnore private String transType; @ExcelProperty("英文名称") private String colName; @ExcelProperty("中文名称") private String colDesc; @ExcelProperty("数据类型") private String colType; @ExcelProperty("字段描述") private String colComment; @ExcelProperty(value = "数据长度",converter = CustomStringIntegerConverter.class) private Integer colLength; @ExcelProperty("默认值") private String colDefaultValue; @ExcelProperty("是否字典项") private String ifDictionary; @ExcelProperty("共享属性") private String shareType; @ExcelProperty("共享条件") private String shareCondition; @ExcelProperty("开放属性") private String ifPublic; @ExcelProperty("开放条件") private String publicCondition; @ExcelProperty("是否可为空") private String emptyEnable; @ExcelProperty("是否主键") private String ifKey; @ExcelProperty("是否归集") private String ifAllowGj; } ``` ### 2-定义一个listener用来监听解析完成的数据 定义一个listener用来监听解析完成的数据,并调用业务service分批次插入数据库。 ``` import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.fastjson.JSON; import com.mrzihan.common.exception.ApplicationException; import com.mrzihan.common.exception.DuplicateException; import com.mrzihan.common.model.DeptModel; import com.mrzihan.common.model.UserModel; import com.mrzihan.common.util.ApplicationContext; import com.mrzihan.common.util.DictionaryHelper; import com.mrzihan.resc.constants.BusinessConstans; import com.mrzihan.resc.enums.DictionaryEnum; import com.mrzihan.resc.model.ImportHistory; import com.mrzihan.resc.model.resRegist.RegistFormModel; import com.mrzihan.resc.model.resRegist.ResTransModel; import com.mrzihan.resc.model.resRegist.TransColumnsModel; import com.mrzihan.resc.pojo.ResImportByNamePojo; import com.mrzihan.resc.service.IDdeptService; import com.mrzihan.resc.service.IPersistentObjectService; import com.mrzihan.resc.service.resManager.IResRegistService; import org.apache.commons.lang3.StringUtils; import org.sevenstar.persistent.db.exception.PersistentException; import org.sevenstar.persistent.db.ibatis.ApplicationUtil; import org.sevenstar.persistent.db.ibatis.IbatisDao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import java.util.*; /** * 2020年新版省平台目录导入功能 * Created by mrzihan on 2020/6/25. * connect to cowboy2014@qq.com */ public class StandardResImportListener extends AnalysisEventListener<ResImportByNamePojo> { private static final Logger logger = LoggerFactory.getLogger(StandardResImportListener.class); public List<ResImportByNamePojo> list = new ArrayList<ResImportByNamePojo>(); //定义一个名字按照资源分条导入 public String importingResName = ""; private static String REGEX_CHINESE = "[\u4e00-\u9fa5]";// 中文正则 ImportHistory history = new ImportHistory(); private final String fileName; private final UserModel user; /** * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 */ private final IPersistentObjectService persistent; private final IResRegistService registService; private final IDdeptService deptService; //记录导入的资源数量 private long resCount = 0; private long validCount = 0; public StandardResImportListener(String fileName) { // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 persistent = (IPersistentObjectService) ApplicationUtil.getBean("persistentObjectService"); registService = (IResRegistService) ApplicationUtil.getBean("resRegistService"); deptService = (IDdeptService) ApplicationUtil.getBean("deptService"); user = ApplicationContext.getUserDomain(); this.fileName = fileName; this.init(); } /** * 初始化日志表 */ private void init(){ history.setFile_name(fileName); history.setCreateUser(user); history.setDept_id(user.getDeptDomain().getId()); // history.setData_amount((long) objects.size()); history.setCreate_date(new Date()); history.setSts("N"); history.setValid_amount(0L); history.setResource_amount(0L); history.setType("res"); persistent.insert(history); } /** * 所有数据解析完成了 都会来调用 * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); logger.info("所有数据解析完成!"); history.setValid_amount(resCount); history.setResource_amount(validCount); history.setDept_id(user.getDeptDomain().getId()); history.setSts("U"); persistent.update(history); } /** * 加上存储数据库 */ private void saveData() { logger.info("{}条数据,开始存储数据库!", list.size()); /** 资源导入阶段 **/ try { //1- registFormModel转换 RegistFormModel registFormModel = convertResFormAndSave(); /** 2- 插入资源数据源信息 **/ if (null != registFormModel.getId()){ ResTransModel resTrans = genTransAndSave(registFormModel); //3- 信息项信息存储 for (ResImportByNamePojo pojo: list){ genColumnsAndSave(pojo, resTrans); } } }catch (Exception e){ String errorMsg = "导入中断:"+e.getMessage(); history.setMessage(errorMsg); persistent.update(history);//此处事务未控制 // 异常增强,中断执行 throw new ApplicationException(errorMsg); } validCount++; resCount++; //记录导入日志 logger.info("存储数据库成功!"); } /** * 这个每一条数据解析都会来调用 * * @param data * one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(ResImportByNamePojo data, AnalysisContext context) { logger.info("解析到一条数据:{}", JSON.toJSONString(data)); // 达到BATCH_COUNT了,即完整的识别了一个资源,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (StringUtils.isEmpty(importingResName)){ importingResName = data.getResTitle(); list.add(data); }else if (StringUtils.isNotEmpty(importingResName) && !importingResName.equals(data.getResTitle())){ saveData(); // 存储完成清理 list list.clear(); importingResName = data.getResTitle(); list.add(data); }else { list.add(data); } } } ``` ### 3-service层业务调用入口 ``` /** * 根据省模板导入 * @param request * @return */ @Override public Map<String, Object> uploadStandardData(HttpServletRequest request) { Map<String, Object> result = new HashMap<>(); CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); if (multipartResolver.isMultipart(request)) { MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request; MultipartFile excel = multiRequest.getFile("file"); //版本1:ResImportPojo.class 版本2:ResImportByNamePojo.class StandardResImportListener listener = new StandardResImportListener(excel.getOriginalFilename()); try { EasyExcel.read(excel.getInputStream(), ResImportByNamePojo.class, listener).sheet(0).headRowNumber(1).doRead(); result.put("msg", "格式校验通过"); } catch (Exception e) { e.printStackTrace(); result.put("msg", e.getMessage()); } } else { result.put("flag", false); result.put("msg", "请上传正确格式的Excel文件"); return result; } result.put("flag", true); return result; }