diff --git a/src/main/java/com/gxwebsoft/credit/controller/CreditNearbyCompanyController.java b/src/main/java/com/gxwebsoft/credit/controller/CreditNearbyCompanyController.java index cf7cebd..2b39a88 100644 --- a/src/main/java/com/gxwebsoft/credit/controller/CreditNearbyCompanyController.java +++ b/src/main/java/com/gxwebsoft/credit/controller/CreditNearbyCompanyController.java @@ -161,6 +161,12 @@ public class CreditNearbyCompanyController extends BaseController { Map urlByCode = ExcelImportSupport.readHyperlinksByHeaderKey(file, usedSheetIndex, usedTitleRows, usedHeadRows, "统一社会信用代码"); Map urlByName = ExcelImportSupport.readHyperlinksByHeaderKey(file, usedSheetIndex, usedTitleRows, usedHeadRows, "企业名称"); + // 避免逐行写库:按批处理,显著降低 SQL 次数与事务开销 + final int chunkSize = 500; + final int mpBatchSize = 500; + List chunkItems = new ArrayList<>(chunkSize); + List chunkRowNumbers = new ArrayList<>(chunkSize); + for (int i = 0; i < list.size(); i++) { CreditNearbyCompanyImportParam param = list.get(i); try { @@ -207,14 +213,64 @@ public class CreditNearbyCompanyController extends BaseController { continue; } + chunkItems.add(item); + chunkRowNumbers.add(excelRowNumber); + if (chunkItems.size() >= chunkSize) { + successCount += persistImportChunk(chunkItems, chunkRowNumbers, companyId, parentId, type, currentTenantId, mpBatchSize, errorMessages); + chunkItems.clear(); + chunkRowNumbers.clear(); + } + } catch (Exception e) { + int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows; + errorMessages.add("第" + excelRowNumber + "行:" + e.getMessage()); + e.printStackTrace(); + } + } + + if (!chunkItems.isEmpty()) { + successCount += persistImportChunk(chunkItems, chunkRowNumbers, companyId, parentId, type, currentTenantId, mpBatchSize, errorMessages); + } + + if (errorMessages.isEmpty()) { + return success("成功导入" + successCount + "条数据", null); + } else { + return success("导入完成,成功" + successCount + "条,失败" + errorMessages.size() + "条", errorMessages); + } + } catch (Exception e) { + e.printStackTrace(); + return fail("导入失败:" + e.getMessage(), null); + } + } + + private int persistImportChunk(List items, + List excelRowNumbers, + Integer companyId, + Integer parentId, + Integer type, + Integer tenantId, + int mpBatchSize, + List errorMessages) { + if (CollectionUtils.isEmpty(items)) { + return 0; + } + try { + return creditNearbyCompanyService.importUpsertChunk(items, companyId, parentId, type, tenantId, mpBatchSize); + } catch (Exception batchException) { + // 批量失败时降级为逐行处理,尽量输出可定位的错误信息 + int successCount = 0; + for (int i = 0; i < items.size(); i++) { + CreditNearbyCompany item = items.get(i); + int excelRowNumber = (excelRowNumbers != null && i < excelRowNumbers.size()) ? excelRowNumbers.get(i) : -1; + try { boolean saved = creditNearbyCompanyService.save(item); if (!saved) { CreditNearbyCompany existing = creditNearbyCompanyService.lambdaQuery() .eq(!ImportHelper.isBlank(item.getCode()), CreditNearbyCompany::getCode, item.getCode()) .eq(ImportHelper.isBlank(item.getCode()), CreditNearbyCompany::getName, item.getName()) - .eq(companyId != null, CreditNearbyCompany::getCompanyId, companyId) - .eq(parentId != null, CreditNearbyCompany::getParentId, parentId) - .eq(type != null, CreditNearbyCompany::getType, type) + .eq(item.getCompanyId() != null, CreditNearbyCompany::getCompanyId, item.getCompanyId()) + .eq(item.getParentId() != null, CreditNearbyCompany::getParentId, item.getParentId()) + .eq(item.getType() != null, CreditNearbyCompany::getType, item.getType()) + .eq(item.getTenantId() != null, CreditNearbyCompany::getTenantId, item.getTenantId()) .one(); if (existing != null) { item.setId(existing.getId()); @@ -227,22 +283,20 @@ public class CreditNearbyCompanyController extends BaseController { successCount++; continue; } - errorMessages.add("第" + excelRowNumber + "行:保存失败"); + if (excelRowNumber > 0) { + errorMessages.add("第" + excelRowNumber + "行:保存失败"); + } else { + errorMessages.add("保存失败"); + } } catch (Exception e) { - int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows; - errorMessages.add("第" + excelRowNumber + "行:" + e.getMessage()); - e.printStackTrace(); + if (excelRowNumber > 0) { + errorMessages.add("第" + excelRowNumber + "行:" + e.getMessage()); + } else { + errorMessages.add(e.getMessage()); + } } } - - if (errorMessages.isEmpty()) { - return success("成功导入" + successCount + "条数据", null); - } else { - return success("导入完成,成功" + successCount + "条,失败" + errorMessages.size() + "条", errorMessages); - } - } catch (Exception e) { - e.printStackTrace(); - return fail("导入失败:" + e.getMessage(), null); + return successCount; } } diff --git a/src/main/java/com/gxwebsoft/credit/service/CreditNearbyCompanyService.java b/src/main/java/com/gxwebsoft/credit/service/CreditNearbyCompanyService.java index 7622af9..ccb03ab 100644 --- a/src/main/java/com/gxwebsoft/credit/service/CreditNearbyCompanyService.java +++ b/src/main/java/com/gxwebsoft/credit/service/CreditNearbyCompanyService.java @@ -39,4 +39,24 @@ public interface CreditNearbyCompanyService extends IService + * 规则:优先按 code 匹配;code 为空时按 name 匹配。 + * + * @param items 待写入数据(需保证 name/code 已做基础清洗/默认值填充) + * @param companyId 企业ID(可空) + * @param parentId 上级ID(可空) + * @param type 类型(可空) + * @param tenantId 租户ID(可空) + * @param batchSize MyBatis-Plus 批处理大小 + * @return 本次写入成功的条数(插入 + 更新) + */ + int importUpsertChunk(List items, + Integer companyId, + Integer parentId, + Integer type, + Integer tenantId, + int batchSize); + } diff --git a/src/main/java/com/gxwebsoft/credit/service/impl/CreditNearbyCompanyServiceImpl.java b/src/main/java/com/gxwebsoft/credit/service/impl/CreditNearbyCompanyServiceImpl.java index 5083b95..f7029e2 100644 --- a/src/main/java/com/gxwebsoft/credit/service/impl/CreditNearbyCompanyServiceImpl.java +++ b/src/main/java/com/gxwebsoft/credit/service/impl/CreditNearbyCompanyServiceImpl.java @@ -1,5 +1,6 @@ package com.gxwebsoft.credit.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gxwebsoft.common.core.web.PageParam; import com.gxwebsoft.common.core.web.PageResult; @@ -8,8 +9,13 @@ import com.gxwebsoft.credit.mapper.CreditNearbyCompanyMapper; import com.gxwebsoft.credit.param.CreditNearbyCompanyParam; import com.gxwebsoft.credit.service.CreditNearbyCompanyService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * 附近企业Service实现 @@ -44,4 +50,116 @@ public class CreditNearbyCompanyServiceImpl extends ServiceImpl items, + Integer companyId, + Integer parentId, + Integer type, + Integer tenantId, + int batchSize) { + if (items == null || items.isEmpty()) { + return 0; + } + + List updates = new ArrayList<>(); + List inserts = new ArrayList<>(); + + List codes = new ArrayList<>(); + List names = new ArrayList<>(); + + for (CreditNearbyCompany item : items) { + if (item == null) { + continue; + } + if (item.getCode() != null) { + item.setCode(item.getCode().trim()); + } + if (item.getName() != null) { + item.setName(item.getName().trim()); + } + + if (item.getCode() != null && !item.getCode().isEmpty()) { + codes.add(item.getCode()); + } else if (item.getName() != null && !item.getName().isEmpty()) { + names.add(item.getName()); + } + } + + Map existingByCode = new HashMap<>(); + Map existingByName = new HashMap<>(); + + if (!codes.isEmpty()) { + LambdaQueryWrapper wrapper = buildImportKeyWrapper(companyId, parentId, type, tenantId); + wrapper.in(CreditNearbyCompany::getCode, codes); + wrapper.select(CreditNearbyCompany::getId, CreditNearbyCompany::getCode); + List existingList = list(wrapper); + for (CreditNearbyCompany existing : existingList) { + if (existing.getCode() != null) { + existingByCode.putIfAbsent(existing.getCode().trim(), existing); + } + } + } + + if (!names.isEmpty()) { + LambdaQueryWrapper wrapper = buildImportKeyWrapper(companyId, parentId, type, tenantId); + wrapper.in(CreditNearbyCompany::getName, names); + wrapper.select(CreditNearbyCompany::getId, CreditNearbyCompany::getName); + List existingList = list(wrapper); + for (CreditNearbyCompany existing : existingList) { + if (existing.getName() != null) { + existingByName.putIfAbsent(existing.getName().trim(), existing); + } + } + } + + for (CreditNearbyCompany item : items) { + if (item == null) { + continue; + } + CreditNearbyCompany existing = null; + if (item.getCode() != null && !item.getCode().isEmpty()) { + existing = existingByCode.get(item.getCode()); + } else if (item.getName() != null && !item.getName().isEmpty()) { + existing = existingByName.get(item.getName()); + } + + if (existing != null) { + item.setId(existing.getId()); + updates.add(item); + } else { + inserts.add(item); + } + } + + if (!updates.isEmpty()) { + // 直接按主键批量更新,避免 saveOrUpdateBatch 产生额外的存在性查询 + updateBatchById(updates, batchSize); + } + if (!inserts.isEmpty()) { + saveBatch(inserts, batchSize); + } + return updates.size() + inserts.size(); + } + + private LambdaQueryWrapper buildImportKeyWrapper(Integer companyId, + Integer parentId, + Integer type, + Integer tenantId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (companyId != null) { + wrapper.eq(CreditNearbyCompany::getCompanyId, companyId); + } + if (parentId != null) { + wrapper.eq(CreditNearbyCompany::getParentId, parentId); + } + if (type != null) { + wrapper.eq(CreditNearbyCompany::getType, type); + } + if (tenantId != null) { + wrapper.eq(CreditNearbyCompany::getTenantId, tenantId); + } + return wrapper; + } + }