【POI】Excel数据导入
Postman请求方式:
Controller接口代码:
/** * /partImport/part/importUpload * @param importFile * @return */ @PostMapping("/part/importUpload") public Map<String, Object> importUpload(@RequestParam(value = "file") MultipartFile importFile) { return partMainDataImportService.importUpload(importFile); }
对象类型:
import org.springframework.web.multipart.MultipartFile;
逻辑思路:
1、Excel转变可获取数据
2、数据结果集插入DB
@Override public Map<String, Object> importUpload(MultipartFile importFile) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); Map<String, Object> resultMap = new HashMap<>(); List<Map<Integer, String>> partMainDatas = null; // Sheet1 配件主数据信息 List<Map<Integer, String>> partExclusivePrice = null; // Sheet2 配件专属价格 List<Map<Integer, String>> partReplacements = null; // Sheet3 配件替换件 try { // 获取数据集合对象 getDataListFromImportExcel导出方法 partMainDatas = getDataListFromImportExcel(importFile, resultMap, 0); partExclusivePrice = getDataListFromImportExcel(importFile, resultMap, 1); partReplacements = getDataListFromImportExcel(importFile, resultMap, 2); Integer integer = insertPartMainData(partMainDatas); resultMap.put("partMainDatas", integer); if (!CollectionUtils.isEmpty(partExclusivePrice)) { Integer integer2 = insertExclusivePrice(partExclusivePrice); resultMap.put("partOwnPrices", integer2); } else { resultMap.put("partOwnPrices", 0); } if (!CollectionUtils.isEmpty(partReplacements)) { Integer integer3 = insertReplacements(partReplacements); resultMap.put("partShifts", integer3); } else { resultMap.put("partShifts", 0); } } catch (Exception exception) { exception.printStackTrace(); return resultMap; } finally { stopWatch.stop(); resultMap.put("execTime", stopWatch.getTotalTimeSeconds() + '秒'); } resultMap.put("200", "导入成功"); return resultMap; }
Excel转换到数据逻辑部分:
/** * 提供给配件主数据导入处理 * @param importFile * @param map 响应结果,如果报错异常,则在这里设置 * @return */ private List<Map<Integer, String>> getDataListFromImportExcel(MultipartFile importFile, Map<String, Object> map, Integer sheetIndex) throws Exception { Workbook workbook = null; // Excel工作薄 Sheet sheet = null; // Excel表单 Sheet页 Row row = null; // 行 Map<Integer, String> dataMap = null; // 表单中的一行数据 List<Map<Integer, String>> dataList = null; // 表单中的所有数据 boolean isValidRow = false; // Excel行中是否是有效数据行 InputStream inputStream = importFile.getInputStream(); // 文件流 String filename = importFile.getOriginalFilename(); // 文件名 // 获取Excel工作簿对象 if ("xls".equalsIgnoreCase(filename.substring(filename.lastIndexOf(".") + 1, filename.length()))) { workbook = new HSSFWorkbook(inputStream); } else { workbook = new XSSFWorkbook(inputStream); } sheet = workbook.getSheetAt(sheetIndex); // 获取指定Sheet页来处理 Boolean flag = false; // 标记,如果检测到空行为true Integer num = 0; dataList = new ArrayList<>(); int physicalNumberOfRows = sheet.getPhysicalNumberOfRows(); for (int i = 1 /* 从表单第二行开始读取数据,读取整个表单中的数据 */; i < physicalNumberOfRows; i++) { num = 0; if(flag){ break; } row = sheet.getRow(i); if (null != row) { dataMap = new HashMap<>(); int physicalNumberOfCells = sheet.getRow(0).getPhysicalNumberOfCells(); for (int j = 0; j < physicalNumberOfCells; j++) { if(null != row.getCell(j) && !StringUtils.isNullOrEmpty(row.getCell(j).getStringCellValue())){ num ++; break; }else if(sheet.getRow(0).getPhysicalNumberOfCells() - 1 == j && num == 0){ flag = true; // true结束 break; } } // 根据模板表头长度,读取一行数据 for (int j = 0; j < sheet.getRow(0).getPhysicalNumberOfCells(); j++) { if(flag){ break; } // 属于零件主数据导入的判断逻辑, sheet主数据 且下标是这个集合内的,判断空则抛出空异常 if ( sheetIndex.equals(0) && this.validateColumnIndexListForMainData.contains(j) && null == row.getCell(j) ) { map.put("5002", "导入的Excel数据(Sheet1配件主数据)必填字段存在空值!!!"); throw new ServiceBizException(ERR_MESSAGE); } else if (sheetIndex.equals(1) && null == row.getCell(j)) { map.put("5003", "导入的Excel数据(Sheet2专属价格)存在空值!!!"); throw new ServiceBizException(ERR_MESSAGE); } else if (sheetIndex.equals(2) && null == row.getCell(j)) { map.put("5004", "导入的Excel数据(Sheet3替换件)存在空值!!!"); throw new ServiceBizException(ERR_MESSAGE); } if (row.getCell(j) == null) { dataMap.put(j, ""); } else { String parseExcel = parseExcel(row.getCell(j)).trim(); if (!StringUtils.isBlank(parseExcel)) { dataMap.put(j, parseExcel); isValidRow = true; } else { dataMap.put(j, ""); } } } /** * 读取完一条记录,如果是有效数据行,则加入返回结果中 */ if (isValidRow) { dataList.add(dataMap); } } } if (CollectionUtils.isEmpty(dataList) && sheetIndex.equals(0)) { map.put("5001", "导入的Excel数据不能为空"); throw new ServiceBizException(ERR_MESSAGE); } return dataList; }
这里有一段解析处理,有点麻烦
private static String parseExcel(Cell cell) { String result = ""; switch (cell.getCellType()) { case HSSFCell.CELL_TYPE_NUMERIC:// 数字类型 if (HSSFDateUtil.isCellDateFormatted(cell)) {// 处理日期格式、时间格式 SimpleDateFormat sdf = null; if (cell.getCellStyle().getDataFormat() == HSSFDataFormat.getBuiltinFormat("h:mm")) { sdf = new SimpleDateFormat("HH:mm"); } else {// 日期 sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } Date date = cell.getDateCellValue(); result = sdf.format(date); } else if (cell.getCellStyle().getDataFormat() == 58) { // 处理自定义日期格式:m月d日(通过判断单元格的格式id解决,id的值是58) SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); double value = cell.getNumericCellValue(); Date date = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(value); result = sdf.format(date); } else { cell.setCellType(XSSFCell.CELL_TYPE_STRING); result = cell.getStringCellValue().toString(); } break; case HSSFCell.CELL_TYPE_STRING:// String类型 result = cell.getRichStringCellValue().toString(); break; case HSSFCell.CELL_TYPE_BLANK: result = ""; break; default: result = ""; break; } return result;
然后第二部分数据封装对象来插入DB:
/** * * @param datas * @return */ private Integer insertPartMainData(List<Map<Integer, String>> datas) throws Exception { Integer affectRows = 0; Class<PartInfoPO> partInfoPOClass = PartInfoPO.class; PartInfoPO partInfoPO ; // 先遍历集合 for (Map<Integer, String> row : datas) { partInfoPO = new PartInfoPO(); partInfoPO.setOwnerCode("-1"); // pmd是每一行的结果 Set<Integer> columns = row.keySet(); // 遍历每一行获取单元格的值 for (Integer column : columns) { String fieldKey = fields.get(column); String fieldValue = row.get(column); if(StringUtils.isNullOrEmpty(fieldValue)){continue;} Field field = partInfoPOClass.getDeclaredField(fieldKey); field.setAccessible(true); Class<?> type = field.getType(); fieldValue = "是".equals(fieldValue) ? YES : "否".equals(fieldValue) ? NO : fieldValue; if (Integer.class.equals(type)) { // fieldValue = "是".equals(fieldValue) ? "10041001" : "否".equals(fieldValue) ? "10041002" : fieldValue; field.set(partInfoPO, Integer.valueOf(fieldValue)); } else if(BigDecimal.class.equals(type)) { // fieldValue = "是".equals(fieldValue) ? "10041001" : "否".equals(fieldValue) ? "10041002" : fieldValue; field.set(partInfoPO, new BigDecimal(fieldValue)); } else if(Date.class.equals(type) ) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd"); field.set(partInfoPO, simpleDateFormat.parse(fieldValue)); } else { field.set(partInfoPO, fieldValue); } } affectRows += partInfoMapper.insert(partInfoPO); } return affectRows; }
2021年11月18日 21点51分 更新:
导入需求有更为明确的逻辑:
这里重新更改了逻辑
以主数据为例,使用了双向取Key的工具Map
private static final BidiMap<String, String> MAIN_DATA_ALIAS = new DualHashBidiMap<>();
配置Excel固定的字段映射匹配:
MAIN_DATA_ALIAS.put("*配件代码", PART_NO); MAIN_DATA_ALIAS.put("*配件名称", "partName"); MAIN_DATA_ALIAS.put("配件英文名", "partNameEn"); MAIN_DATA_ALIAS.put("计量单位代码", "unitCode"); MAIN_DATA_ALIAS.put("最小包装数", "minPackage"); MAIN_DATA_ALIAS.put("*适用车型", "applicableVehicleType"); MAIN_DATA_ALIAS.put("*型号", "model"); MAIN_DATA_ALIAS.put("替换件代码", "optionPartNo"); MAIN_DATA_ALIAS.put("替换方式", "optionDirect"); MAIN_DATA_ALIAS.put("单车用量", "quantityPerCar"); MAIN_DATA_ALIAS.put("*采购价(含税)", "planPriceTaxed"); MAIN_DATA_ALIAS.put("*采购价(不含税)", "planPriceNoTaxed"); MAIN_DATA_ALIAS.put("*厂端建议销售价(含税)", "limitPrice"); MAIN_DATA_ALIAS.put("*建议零售价(含税)", "suggestRetailPriceTaxed"); MAIN_DATA_ALIAS.put("*建议零售价(不含税)", "suggestRetailPriceNoTaxed"); MAIN_DATA_ALIAS.put("*三包标志", "isTripleGuarantee"); MAIN_DATA_ALIAS.put("*不可索赔标志", "isClaim"); MAIN_DATA_ALIAS.put("*是否有效", "partStatus"); MAIN_DATA_ALIAS.put("*是否危险品", "isUnsafe"); MAIN_DATA_ALIAS.put("*是否追溯件", "isBack"); MAIN_DATA_ALIAS.put("*是否DD件", "isDd"); MAIN_DATA_ALIAS.put("*是否监控件", "isMonitor"); MAIN_DATA_ALIAS.put("*是否可销售", "isCSale"); MAIN_DATA_ALIAS.put("*是否可采购", "isCPurchase"); MAIN_DATA_ALIAS.put("*是否为电池零件", "isBattery"); MAIN_DATA_ALIAS.put("备注", "remark"); MAIN_DATA_ALIAS.put("*停用", "isDisable"); MAIN_DATA_ALIAS.put("停用日期", "disableDate");
其中带星号的标记为必填字段
对于Excel的数据约束控制,后台不做处理了,Excel来设置数据有效性控制即可
数值类型在DB中使用了Decimal存储,在PO封装的时候可以使用String处理,就是要考虑空值情况
为了方便,只剩下日期类型必须Date类型存储
DATE_FIELDS.add("disableDate"); DATE_FIELDS.add("beginAt"); DATE_FIELDS.add("endAt");
读取的工作由Hutool工具完成,代码简单也好用
/** * 自定义设置Reader获取 * @param inputStream Excel输入流 * @param sheetIndex Sheet页 下标 * @param alias 字段 头列 别名 映射集 * @return */ private ExcelReader getReaderAfterCustomSetting(InputStream inputStream, Integer sheetIndex, Map<String, String> alias) { ExcelReader reader = ExcelUtil.getReader(inputStream, sheetIndex); reader.setHeaderAlias(alias); reader.setIgnoreEmptyRow(true); return reader; }
主数据读取Map转PO的逻辑就是这样
因为不能直接转PO,有字段要翻译,所以PO封装必须依靠反射实现
注意我的空值逻辑Flag,还有Jdbc把值存储为其他类型,则无法直接赋值,所以最后还val.toString()这样再获取一下
private List<PartInfoPO> getMainDataListNew(List<Map<String, Object>> mainDataList) { List<PartInfoPO> partInfoPOList = new ArrayList<>(); Class<PartInfoPO> partInfoPOClass = PartInfoPO.class; mainDataList.forEach(rowMap -> { PartInfoPO partInfoPO = new PartInfoPO(); rowMap.keySet().forEach(colKey -> { try { if (StringUtils.isNullOrEmpty(colKey)) return; Object val = rowMap.get(colKey); Field declaredField = partInfoPOClass.getDeclaredField(colKey); ReflectionUtils.makeAccessible(declaredField); boolean isEmpty = null == val || "".equals(val); if (DATE_FIELDS.contains(colKey) && !isEmpty) { val = DateUtil.parse(val.toString()); declaredField.set(partInfoPO, val); } else if ("optionDirect".equals(colKey) && !isEmpty) { val = "单向".equals(val) ? PART_DIRECT_1 : "双向".equals(val) ? PART_DIRECT_2 : val; declaredField.set(partInfoPO, String.valueOf(val)); } else if (DICT_1004_FIELDS.contains(colKey)) { if ("是".equals(val)) val = YES; else if ("否".equals(val)) val = NO; declaredField.set(partInfoPO, val); } else if (!isEmpty) { declaredField.set(partInfoPO, val.toString()); } } catch (Exception e) { e.printStackTrace(); } }); partInfoPOList.add(partInfoPO); }); return partInfoPOList; }
专属价格加入了其他业务逻辑的需要:
private Set<PartOwnPricePO> getPartOwnPriceList(List<Map<String, Object>> partOwnPriceList) { Date currentDate = new Date(); Set<PartOwnPricePO> list = new HashSet<>(); Class<PartOwnPricePO> partOwnPricePOClass = PartOwnPricePO.class; partOwnPriceList.forEach(rowMap -> { PartOwnPricePO partOwnPricePO = new PartOwnPricePO(); rowMap.keySet().forEach(colKey -> { try { Object val = rowMap.get(colKey); // 暂时不管Flag和空格 if (StringUtils.isNullOrEmpty(colKey)) return; Field declaredField = partOwnPricePOClass.getDeclaredField(colKey); ReflectionUtils.makeAccessible(declaredField); if (DATE_FIELDS.contains(colKey)) { val = DateUtil.parse(val.toString()); declaredField.set(partOwnPricePO, val); } else if (IS_DELETE.equals(colKey) && !StringUtils.isNullOrEmpty(val) && "Y".equals(val)) { /** * 设置是否删除标记为是 * d.当存在时,将所有对应数据的删除标记更新为“是”,并且如果对应的结束时间在导入时间之前则对于时间不进行更新。如果在导入时间之后,则更新对应结束时间。 */ declaredField.set(partOwnPricePO, YES); int i = currentDate.compareTo(partOwnPricePO.getEndAt());// -1 currentDate < getEndAt if (i > 0) { partOwnPricePO.setEndAt(null); // 不更新结束时间 } } else { declaredField.set(partOwnPricePO, val.toString()); } } catch (Exception exception) { exception.printStackTrace(); } }); /** * 不设置默认经销商 * partOwnPricePO.setOwnerCode("-1"); */ list.add(partOwnPricePO); }); return list; }
然后再是导入逻辑:
导入是允许重复插入的,类似下发操作
List<PartInfoPO> mainDataListNew = getMainDataListNew(mainDataList); /** * a.在配件主档导入时,判断配件代码是否存在。不存在整行新增;存在时,对应配件代码进行更新 */ // 获取所有配件代码 List<String> allPartNo = partInfoMapper.selectList(new QueryWrapper<PartInfoPO>().select(PART_NO1)).stream().map(PartInfoPO::getPartNo).collect(Collectors.toList()); // 有则更新,无则插入 mainDataListNew.forEach(partInfoPO -> { String partNo = partInfoPO.getPartNo(); if (allPartNo.contains(partNo) ) { partInfoMapper.update(partInfoPO, new QueryWrapper<PartInfoPO>().eq(PART_NO1, partNo)); } else { // 新增数据 没有经销商代码,则设置默认-1 partInfoPO.setOwnerCode("-1"); partInfoMapper.insert(partInfoPO); } });
配件专属价格还有一个时间范围的要求:
<select id="bizValidationSql" resultType="java.lang.Long"> SELECT COUNT(*) FROM tm_part_own_price WHERE ( #{params.beginAt} BETWEEN BEGIN_AT AND END_AT OR #{params.endAt} BETWEEN BEGIN_AT AND END_AT) AND OWNER_CODE = #{params.ownerCode} AND PART_NO = #{params.partNo} </select>
就是插入DB的时候需要判断他的时间范围是不是交叉了
例如记录1 是 1 - 3月这个时间段,后面的记录不能和记录1重合,开始时间要么小于1月要么大于3月
结束时间也是如此,不能踩到1 - 3月这个范围
这个SQL就用来查询是否存在这个情况,如果存在则这个数据是交叉的,不允许插入
/** * b.导入专属价格信息,判断【配件代码+经销商代码+开始时间+结束时间】是否存在,存在时进行更新。不存在时新增数据 * c.导入专属价格信息时,同一批次不能存在【配件代码+经销商代码+开始时间+结束时间】相同数据且【配件代码+经销商代码】相同是开始时间及结束时间不能与存在交叉情况。如以上两种情况出现则导入失败,并提示对应错误信息 * d.在导入专属价格时,当删除标记被选中时,配件代码及经销商代码为必填。其余字段不校验必填。当导入时需要校验配件代码及经销商代码是否存在:当存在时,将所有对应数据的删除标记更新为“是”,并且如果对应的结束时间在导入时间之前则对于时间不进行 */ Set<PartOwnPricePO> priceList = getPartOwnPriceList(ownPriceList); List<PartOwnPricePO> errorPartNoList = new ArrayList<>(); /** * 逻辑校验与操作 */ priceList.forEach(partOwnPricePO -> { QueryWrapper<PartOwnPricePO> eq = new QueryWrapper<PartOwnPricePO>() .eq("OWNER_CODE", partOwnPricePO.getOwnerCode()) .eq(PART_NO1, partOwnPricePO.getPartNo()) .eq("BEGIN_AT", partOwnPricePO.getBeginAt()) .or().eq("END_AT", partOwnPricePO.getEndAt()); List<PartOwnPricePO> sameList = partOwnPriceMapper.selectList(eq); if (!sameList.isEmpty()) { PartOwnPricePO poFromDB = sameList.get(0); // 目的是更新价格 与结束时间调整 poFromDB.setOwnPrice(partOwnPricePO.getOwnPrice()); poFromDB.setEndAt(partOwnPricePO.getEndAt()); partOwnPriceMapper.update(poFromDB, eq); return; } // 是否交叉 else if (partOwnPriceMapper.bizValidationSql(partOwnPricePO) > 0) { errorPartNoList.add(partOwnPricePO); return; } partOwnPriceMapper.insert(partOwnPricePO); }); result.put("errorList", errorPartNoList);
2021年12月6日 15点34分 更新
SonarLint代码检查之后不允许使用反射API操作,代码进行了重构
主业务代码压缩的很少了
/** * LoginInfoDto loginInfo = ApplicationContextHelper.getBeanByType(LoginInfoDto.class); * @param importFile * @return */ @Override public Map<String, Object> importUpload(MultipartFile importFile) { Map<String, Object> result = new HashMap<>(); try { List<Map<String, Object>> mainDataList = readAllByCustomSetting(importFile.getInputStream(), 0, MAIN_DATA_ALIAS); List<Map<String, Object>> ownPriceList = readAllByCustomSetting(importFile.getInputStream(), 1, OWN_PRICE_ALIAS); // 校验 importDataValidation1(mainDataList, result); if (!StringUtils.isNullOrEmpty(result.get(ERROR))) { return result; } importDataValidation2(ownPriceList, result); if (!StringUtils.isNullOrEmpty(result.get(ERROR))) { return result; } // 翻译,过滤 mainDataList = getMainDataList2(mainDataList); ownPriceList = getOwnPriceList(ownPriceList); // 导入 mainDataImport2(mainDataList); ownPriceImport2(ownPriceList); replaceImport(mainDataList); } catch (Exception exception) { logger.error(exception.getMessage()); } result.put(SUCCESS, "导入成功"); return result; }
就分为总体三步走:
校验 - 过滤翻译处理 - 导入
1、校验部分:
写在一起被SonarLint警告方法复杂程度超过了15,要求拆解
然后单元格的判断场景过多且复杂,这里就使用组合条件进行判断
/** * 校验配件主数据 * @param mainDataList * @param result */ private void importDataValidation1(List<Map<String, Object>> mainDataList, Map<String, Object> result) { if (mainDataList.isEmpty()) { result.put(ERROR, "配件主数据Sheet页为空!!!"); return; } for (Map<String, Object> rowMap : mainDataList) { for (Map.Entry<String, Object> entry : rowMap.entrySet()) { String key = entry.getKey(); if ("".equals(key)) continue; String excelColTitle = MAIN_DATA_ALIAS.getKey(key); Object val = entry.getValue(); boolean nullVal = StringUtils.isNullOrEmpty(val); // 空值判断 boolean requiredCol = excelColTitle.contains("*"); // 必填列判断 boolean flag = extractedMainDataValid(result, rowMap, key, excelColTitle, val, nullVal, requiredCol); if (flag) return; } } } private boolean extractedMainDataValid(Map<String, Object> result, Map<String, Object> rowMap, String key, String excelColTitle, Object val, boolean nullVal, boolean requiredCol) { // 一般空值校验 if (nullVal && requiredCol) { result.put(ERROR, COMMON_TIP_PREFIX + excelColTitle + "】列存在必填项空值,请检查后重新上传!"); return true; } // 替换件和替换方式为非必填项,但是填写任一项,皆为必填 else if ( (OPTION_PART_NO.equals(key) && !nullVal) && StringUtils.isNullOrEmpty(rowMap.get(OPTION_DIRECT)) // 当替换件不为空 且 替换方向是空值时 || (OPTION_DIRECT.equals(key) && !nullVal) && StringUtils.isNullOrEmpty(rowMap.get(OPTION_PART_NO)) // 或者替换方式不为空 且 替换件是空值时 ) { result.put(ERROR, "配件主数据Sheet页,替换件和替换方式需要一起填写,请重新检查!"); return true; } else if (OPTION_DIRECT.equals(key) && !nullVal && !DIRECT_FIELDS.contains(val.toString())) { result.put(ERROR, "配件主数据Sheet页,【替换方式】存在错误的状态值[单向,双向],请检查后重新上传!"); return true; } // 数值校验 else if (NUMBER_FIELDS.contains(key) && !nullVal && !val.toString().matches(NUMBER_REGEXP)) { result.put(ERROR, COMMON_TIP_PREFIX + excelColTitle + "】列存在错误的数值,请检查后重新上传!"); return true; } // 是否字段校验处理 else if (DICT_1004_FIELDS.contains(key) && !nullVal && !YES_NO_FIELDS.contains(val.toString()) ) { result.put(ERROR, COMMON_TIP_PREFIX + excelColTitle + "】列存在错误的状态值[是,否],请检查后重新上传!"); return true; } // 日期校验 else if (DISABLE_DATE.equals(key) && !nullVal && !(val instanceof DateTime)) { result.put(ERROR, "配件主数据Sheet页【停用日期】列存在错误的日期值,请检查后重新上传!"); return true; } return false; }
2、翻译处理部分:
使用Stream的API处理,需要更改元素内容,
删除空Key是因为这个Hutool版本的Excel解析导入有点BUG,会读空串头
/** * 封装可导入的数据 * @param mainDataList * @return */ private List<Map<String, Object>> getMainDataList2(List<Map<String, Object>> mainDataList) { return mainDataList.stream().map(row -> { // map() peek() row.remove(""); // 翻译替换件方向 row.computeIfPresent(OPTION_DIRECT, (key, value) -> DIRECT_FIELDS.get(0).equals(value) ? PART_DIRECT_1 : PART_DIRECT_2); // 翻译日期 row.computeIfPresent(DISABLE_DATE, (key, value) -> DateUtil.parse(value.toString())); // 翻译是否 row.keySet().forEach(s -> { if (DICT_1004_FIELDS.contains(s)) row.computeIfPresent(s, (key, value) -> YES_NO_FIELDS.get(0).equals(value) ? YES : NO); }); // 停用缺省设置 if (StringUtils.isNullOrEmpty(row.get(IS_DISABLE))) row.put(IS_DISABLE, NO); // 删除空Val的Key row.entrySet().removeIf(next -> StringUtils.isNullOrEmpty(next.getValue())); /** * // 缺省参数设置:供应商代码 + 供应商名称 * if (StringUtils.isNullOrEmpty(row.get("providerCode"))) row.put("providerCode", "000000000000"); * if (StringUtils.isNullOrEmpty(row.get("providerName"))) row.put("providerCode", "主机厂"); */ return row; }).collect(Collectors.toList()); }
3、导入部分:
导入操作是 插入 + 更新一起处理,这个在程序层处理有点吃力
改用SQL去处理了:
/** * 导入主数据 * @param mainDataList */ private void mainDataImport2(List<Map<String, Object>> mainDataList) { mainDataList.forEach(currentRecord -> partInfoMapper.insertAndUpdateByMap(currentRecord)); }
XmlMapper写好的SQL:
因为一些字段不是导入的必填项,需要动态判断处理
<insert id="insertAndUpdateByMap" parameterType="map"> INSERT INTO `tm_part_info` ( PART_NO, OWNER_CODE, PART_NAME, APPLICABLE_VEHICLE_TYPE, MODEL, PLAN_PRICE_TAXED, PLAN_PRICE_NO_TAXED, CLAIM_PRICE, LIMIT_PRICE, SUGGEST_RETAIL_PRICE_TAXED, SUGGEST_RETAIL_PRICE_NO_TAXED, IS_TRIPLE_GUARANTEE, IS_CLAIM, PART_STATUS, IS_UNSAFE, IS_BACK, IS_DD, IS_MONITOR, IS_C_SALE, IS_C_PURCHASE, IS_BATTERY, <if test="partNameEn != null"> PART_NAME_EN, </if> <if test="unitCode != null"> UNIT_CODE, </if> <if test="minPackage != null"> MIN_PACKAGE, </if> <if test="quantityPerCar != null"> QUANTITY_PER_CAR, </if> <if test="optionPartNo != null"> OPTION_PART_NO, </if> <if test="optionDirect != null"> OPTION_DIRECT, </if> <if test="remark != null"> REMARK, </if> <if test="disableDate != null"> DISABLE_DATE, </if> IS_DISABLE ) VALUES ( #{partNo}, '-1', #{partName}, #{applicableVehicleType}, #{model}, #{planPriceTaxed}, #{planPriceNoTaxed}, #{claimPrice}, #{limitPrice}, #{suggestRetailPriceTaxed}, #{suggestRetailPriceNoTaxed}, #{isTripleGuarantee}, #{isClaim}, #{partStatus}, #{isUnsafe}, #{isBack}, #{isDd}, #{isMonitor}, #{isCSale}, #{isCPurchase}, #{isBattery}, <if test="partNameEn != null"> #{partNameEn}, </if> <if test="unitCode != null"> #{unitCode}, </if> <if test="minPackage != null"> #{minPackage}, </if> <if test="quantityPerCar != null"> #{quantityPerCar}, </if> <if test="optionPartNo != null"> #{optionPartNo}, </if> <if test="optionDirect != null"> #{optionDirect}, </if> <if test="remark != null"> #{remark}, </if> <if test="disableDate != null"> #{disableDate}, </if> #{isDisable} ) ON DUPLICATE KEY UPDATE PART_NAME = #{partName}, APPLICABLE_VEHICLE_TYPE = #{applicableVehicleType}, MODEL = #{model}, PLAN_PRICE_TAXED = #{planPriceTaxed}, PLAN_PRICE_NO_TAXED = #{planPriceNoTaxed}, CLAIM_PRICE = #{claimPrice}, LIMIT_PRICE = #{limitPrice}, SUGGEST_RETAIL_PRICE_TAXED = #{suggestRetailPriceTaxed}, SUGGEST_RETAIL_PRICE_NO_TAXED = #{suggestRetailPriceNoTaxed}, IS_TRIPLE_GUARANTEE = #{isTripleGuarantee}, IS_CLAIM = #{isClaim}, PART_STATUS = #{partStatus}, IS_UNSAFE = #{isUnsafe}, IS_BACK = #{isBack}, IS_DD = #{isDd}, IS_MONITOR = #{isMonitor}, IS_C_SALE = #{isCSale}, IS_C_PURCHASE = #{isCPurchase}, IS_BATTERY = #{isBattery}, <if test="partNameEn != null"> PART_NAME_EN = #{partNameEn}, </if> <if test="unitCode != null"> UNIT_CODE = #{unitCode}, </if> <if test="minPackage != null"> MIN_PACKAGE = #{minPackage}, </if> <if test="quantityPerCar != null"> QUANTITY_PER_CAR = #{quantityPerCar}, </if> <if test="optionPartNo != null"> OPTION_PART_NO = #{optionPartNo}, </if> <if test="optionDirect != null"> OPTION_DIRECT = #{optionDirect}, </if> <if test="remark != null"> REMARK = #{remark}, </if> <if test="disableDate != null"> DISABLE_DATE = #{disableDate}, </if> IS_DISABLE = #{isDisable}, DISTRIBUTION_STATUS = 0 </insert>