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>

【POI】Excel数据导入的更多相关文章

  1. 将Excel数据导入mysql数据库的几种方法

    将Excel数据导入mysql数据库的几种方法 “我的面试感悟”有奖征文大赛结果揭晓! 前几天需要将Excel表格中的数据导入到mysql数据库中,在网上查了半天,研究了半天,总结出以下几种方法,下面 ...

  2. java实现EXCEL数据导入到数据库中的格式问题的解决

    之前作为项目甲方,加之java接触不多,在java web开发方面都是打下手的份. 对于EXCEL数据导入到数据库这个问题一直老是出现格式原因而导入失败也是未免惆怅,开发团队也是只说回去检查一下格式. ...

  3. kettle将Excel数据导入oracle

    导读 Excel数据导入Oracle数据库的方法: 1.使用PL SQL 工具附带的功能,效率比较低 可参考这篇文章的介绍:http://www.2cto.com/database/201212/17 ...

  4. 效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中【附源代码下载】) 转

    效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中[附源代码下载])    本文目录: (一)背景 (二)数据库数据导入到Excel的方法比较   ...

  5. Java代码实现excel数据导入到Oracle

    1.首先需要两个jar包jxl.jar,ojdbc.jar(注意版本,版本不合适会报版本错误)2.代码: Java代码   import java.io.File; import java.io.Fi ...

  6. 如何把excel数据导入数据库

    这里介绍2种把excel数据导入oracle数据库的方法. 1. 在excel中生成sql语句. 1)在数据列的右侧,第一行的任何位置输入="insert into table(xx,yyy ...

  7. excel 数据导入数据表

    环境: Windows server 2012  rm sql server  2012 excel 数据导入数据表 INSERT INTO [dbo].[AdminUser] SELECT [Adm ...

  8. C#使用oledb方式将excel数据导入到datagridview后数据被截断为 255 个字符

    问题描述:在使用oledb方式将excel数据导入到datagridview中,在datagridview单元格中的数据没有显示全,似乎只截取了数据源中的一段 解决方案:1.关于该问题,微软官方答案: ...

  9. excel数据导入到sqlserver中---------工作笔记

    调用页面: using System; using System.Collections.Generic; using System.Linq; using System.Web; using Sys ...

  10. c#将Excel数据导入到数据库的实现代码(OleDb)

    sing System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web ...

随机推荐

  1. 推荐一款免费的终端工具-Windterm

    Windterm 提到SSH终端工具,大家一定会想到putty.xshell.MobaXterm这些产品.但是当我使用Windterm后,我彻底改变了我对之前使用SSH终端工具的认知,其强大的能力,彻 ...

  2. 接口自动化之request模块

    1.安装 方式一.命令行直接 pip install requests 方式二.PyCharm中,File >> Settings >> Project:Practice &g ...

  3. maven常用命令大全(附详细解释)

    1.常用打包命令 mvn clean package -Dmaven.test.skip=true -- 跳过单测打包mvn clean install -Dmaven.test.skip=true ...

  4. 何时/如何使用 std::enable_shared_from_this<T>?

    要点回顾 继承自 std::enable_shared_from_this<T> 的类能够在其自身实例中通过 std::shared_from_this 方法创建一个指向自己的 std:: ...

  5. 利用.htaccess绑定子域名到子目录

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 利用.htaccess绑定子域名到子目录 日期:2018- ...

  6. GIT 生成变更历史文件清单

    脚本搞定git文件版本变化信息,解决部署种变更的审核和统计信息工作复杂问题 git diff --name-status --ignore-cr-at-eol --ignore-space-at-eo ...

  7. maven项目创建默认目录结构

    maven项目创建默认目录结构命令 项目文件夹未创建情况下 mvn \ archetype:generate \ -DgroupId=com.lits.parent \ -DartifactId=my ...

  8. 终端读取iOS项目所有设置参数(版本号、应用名等)

    在某些场景下(比如自动化打包等),我们需要从终端来读取到iOS项目的数据,首先先上代码 xcodebuild -showBuildSettings -target 项目target 但有时候我们需要将 ...

  9. 2019银川区域赛BDFGHIKN题解

    B.So Easy 题目大意:给你一个正方形矩阵,初始都是0,题目对这个矩阵做了许多次操作,每次操作把行+1或列+1.其中有一个元素被隐藏了,你需要找出这个被隐藏的元素并判断它在操作之后应该是多少. ...

  10. Android 耳机驱动知识

    Android 耳机驱动知识 2015-03-06 工作以后接手的第一个驱动就是android平台下耳机的插拔检测和按键检测.这部分涉及的硬件知识比较简单,但是软件上对中断的处理,软件检测的鲁棒性,都 ...