refactor: 重构Service层使用ON DUPLICATE KEY UPDATE
- 更新模式直接调用importPersonBatch/importEntityBatch - 移除'先删除再插入'逻辑,代码简化约50% - 添加辅助方法saveBatchWithUpsert/getExistingPersonIdsFromDb - 添加createFailureVO重载方法简化失败记录创建 变更详情: - CcdiIntermediaryPersonImportServiceImpl: 重构importPersonAsync方法 - CcdiIntermediaryEntityImportServiceImpl: 重构importEntityAsync方法 - 两个Service均采用统一的处理模式 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
1324
doc/plans/2026-02-08-intermediary-import-upsert-implementation.md
Normal file
1324
doc/plans/2026-02-08-intermediary-import-upsert-implementation.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -45,17 +45,14 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
Boolean isUpdateSupport,
|
Boolean isUpdateSupport,
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName) {
|
String userName) {
|
||||||
List<CcdiEnterpriseBaseInfo> newRecords = new ArrayList<>();
|
List<CcdiEnterpriseBaseInfo> validRecords = new ArrayList<>();
|
||||||
List<CcdiEnterpriseBaseInfo> updateRecords = new ArrayList<>();
|
|
||||||
List<IntermediaryEntityImportFailureVO> failures = new ArrayList<>();
|
List<IntermediaryEntityImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
// 1. 批量查询已存在的统一社会信用代码
|
// 1. 批量查询已存在的统一社会信用代码
|
||||||
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
|
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
|
||||||
|
|
||||||
// 2. 分类数据
|
// 2. 验证并转换数据
|
||||||
for (int i = 0; i < excelList.size(); i++) {
|
for (CcdiIntermediaryEntityExcel excel : excelList) {
|
||||||
CcdiIntermediaryEntityExcel excel = excelList.get(i);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 验证数据
|
// 验证数据
|
||||||
validateEntityData(excel, isUpdateSupport, existingCreditCodes);
|
validateEntityData(excel, isUpdateSupport, existingCreditCodes);
|
||||||
@@ -67,57 +64,53 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
entity.setDataSource("IMPORT");
|
entity.setDataSource("IMPORT");
|
||||||
entity.setEntSource("INTERMEDIARY");
|
entity.setEntSource("INTERMEDIARY");
|
||||||
entity.setCreatedBy(userName);
|
entity.setCreatedBy(userName);
|
||||||
|
if (existingCreditCodes.contains(excel.getSocialCreditCode()) && isUpdateSupport) {
|
||||||
if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
|
entity.setUpdatedBy(userName);
|
||||||
if (isUpdateSupport) {
|
|
||||||
// 更新模式:设置更新人
|
|
||||||
entity.setUpdatedBy(userName);
|
|
||||||
updateRecords.add(entity);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("该统一社会信用代码已存在");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newRecords.add(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validRecords.add(entity);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
IntermediaryEntityImportFailureVO failure = new IntermediaryEntityImportFailureVO();
|
failures.add(createFailureVO(excel, e.getMessage()));
|
||||||
BeanUtils.copyProperties(excel, failure);
|
|
||||||
failure.setErrorMessage(e.getMessage());
|
|
||||||
failures.add(failure);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 批量插入新数据
|
// 3. 根据isUpdateSupport选择处理方式
|
||||||
if (!newRecords.isEmpty()) {
|
if (isUpdateSupport) {
|
||||||
saveBatch(newRecords, 500);
|
// 更新模式:直接批量导入,数据库自动处理INSERT或UPDATE
|
||||||
|
if (!validRecords.isEmpty()) {
|
||||||
|
saveBatchWithUpsert(validRecords, 500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 仅新增模式:先查询已存在的记录,对冲突的抛出异常
|
||||||
|
Set<String> actualExistingCreditCodes = getExistingCreditCodesFromDb(validRecords);
|
||||||
|
List<CcdiEnterpriseBaseInfo> actualNewRecords = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CcdiEnterpriseBaseInfo record : validRecords) {
|
||||||
|
if (actualExistingCreditCodes.contains(record.getSocialCreditCode())) {
|
||||||
|
// 记录到失败列表
|
||||||
|
failures.add(createFailureVO(record, "该统一社会信用代码已存在"));
|
||||||
|
} else {
|
||||||
|
actualNewRecords.add(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量插入新记录
|
||||||
|
if (!actualNewRecords.isEmpty()) {
|
||||||
|
saveBatch(actualNewRecords, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 批量更新已有数据(先删除再插入)
|
// 4. 保存失败记录到Redis
|
||||||
if (!updateRecords.isEmpty() && isUpdateSupport) {
|
|
||||||
// 先批量删除已存在的记录
|
|
||||||
List<String> creditCodes = updateRecords.stream()
|
|
||||||
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> deleteWrapper = new LambdaQueryWrapper<>();
|
|
||||||
deleteWrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, creditCodes);
|
|
||||||
entityMapper.delete(deleteWrapper);
|
|
||||||
|
|
||||||
// 批量插入更新后的数据
|
|
||||||
entityMapper.insertBatch(updateRecords);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 保存失败记录到Redis
|
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
String failuresKey = "import:intermediary-entity:" + taskId + ":failures";
|
String failuresKey = "import:intermediary-entity:" + taskId + ":failures";
|
||||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 更新最终状态
|
// 5. 更新最终状态
|
||||||
ImportResult result = new ImportResult();
|
ImportResult result = new ImportResult();
|
||||||
result.setTotalCount(excelList.size());
|
result.setTotalCount(excelList.size());
|
||||||
result.setSuccessCount(newRecords.size() + updateRecords.size());
|
result.setSuccessCount(validRecords.size() - failures.size());
|
||||||
result.setFailureCount(failures.size());
|
result.setFailureCount(failures.size());
|
||||||
|
|
||||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||||
@@ -183,6 +176,58 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存(使用ON DUPLICATE KEY UPDATE)
|
||||||
|
*/
|
||||||
|
private void saveBatchWithUpsert(List<CcdiEnterpriseBaseInfo> list, int batchSize) {
|
||||||
|
for (int i = 0; i < list.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, list.size());
|
||||||
|
List<CcdiEnterpriseBaseInfo> subList = list.subList(i, end);
|
||||||
|
entityMapper.importEntityBatch(subList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据库获取已存在的统一社会信用代码
|
||||||
|
*/
|
||||||
|
private Set<String> getExistingCreditCodesFromDb(List<CcdiEnterpriseBaseInfo> records) {
|
||||||
|
List<String> creditCodes = records.stream()
|
||||||
|
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
|
||||||
|
.filter(StringUtils::isNotEmpty)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (creditCodes.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, creditCodes);
|
||||||
|
List<CcdiEnterpriseBaseInfo> existing = entityMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
return existing.stream()
|
||||||
|
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建失败记录VO
|
||||||
|
*/
|
||||||
|
private IntermediaryEntityImportFailureVO createFailureVO(CcdiIntermediaryEntityExcel excel, String errorMsg) {
|
||||||
|
IntermediaryEntityImportFailureVO failure = new IntermediaryEntityImportFailureVO();
|
||||||
|
BeanUtils.copyProperties(excel, failure);
|
||||||
|
failure.setErrorMessage(errorMsg);
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建失败记录VO(重载方法)
|
||||||
|
*/
|
||||||
|
private IntermediaryEntityImportFailureVO createFailureVO(CcdiEnterpriseBaseInfo record, String errorMsg) {
|
||||||
|
CcdiIntermediaryEntityExcel excel = new CcdiIntermediaryEntityExcel();
|
||||||
|
BeanUtils.copyProperties(record, excel);
|
||||||
|
return createFailureVO(excel, errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量保存
|
* 批量保存
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,17 +46,14 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
Boolean isUpdateSupport,
|
Boolean isUpdateSupport,
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName) {
|
String userName) {
|
||||||
List<CcdiBizIntermediary> newRecords = new ArrayList<>();
|
List<CcdiBizIntermediary> validRecords = new ArrayList<>();
|
||||||
List<CcdiBizIntermediary> updateRecords = new ArrayList<>();
|
|
||||||
List<IntermediaryPersonImportFailureVO> failures = new ArrayList<>();
|
List<IntermediaryPersonImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
// 1. 批量查询已存在的证件号
|
// 1. 批量查询已存在的证件号
|
||||||
Set<String> existingPersonIds = getExistingPersonIds(excelList);
|
Set<String> existingPersonIds = getExistingPersonIds(excelList);
|
||||||
|
|
||||||
// 2. 分类数据
|
// 2. 验证并转换数据
|
||||||
for (int i = 0; i < excelList.size(); i++) {
|
for (CcdiIntermediaryPersonExcel excel : excelList) {
|
||||||
CcdiIntermediaryPersonExcel excel = excelList.get(i);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 验证数据
|
// 验证数据
|
||||||
validatePersonData(excel, isUpdateSupport, existingPersonIds);
|
validatePersonData(excel, isUpdateSupport, existingPersonIds);
|
||||||
@@ -67,57 +64,53 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
// 设置数据来源和审计字段
|
// 设置数据来源和审计字段
|
||||||
intermediary.setDataSource("IMPORT");
|
intermediary.setDataSource("IMPORT");
|
||||||
intermediary.setCreatedBy(userName);
|
intermediary.setCreatedBy(userName);
|
||||||
|
if (existingPersonIds.contains(excel.getPersonId()) && isUpdateSupport) {
|
||||||
if (existingPersonIds.contains(excel.getPersonId())) {
|
intermediary.setUpdatedBy(userName);
|
||||||
if (isUpdateSupport) {
|
|
||||||
// 更新模式:设置更新人
|
|
||||||
intermediary.setUpdatedBy(userName);
|
|
||||||
updateRecords.add(intermediary);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("该证件号码已存在");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newRecords.add(intermediary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validRecords.add(intermediary);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO();
|
failures.add(createFailureVO(excel, e.getMessage()));
|
||||||
BeanUtils.copyProperties(excel, failure);
|
|
||||||
failure.setErrorMessage(e.getMessage());
|
|
||||||
failures.add(failure);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 批量插入新数据
|
// 3. 根据isUpdateSupport选择处理方式
|
||||||
if (!newRecords.isEmpty()) {
|
if (isUpdateSupport) {
|
||||||
saveBatch(newRecords, 500);
|
// 更新模式:直接批量导入,数据库自动处理INSERT或UPDATE
|
||||||
|
if (!validRecords.isEmpty()) {
|
||||||
|
saveBatchWithUpsert(validRecords, 500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 仅新增模式:先查询已存在的记录,对冲突的抛出异常
|
||||||
|
Set<String> actualExistingPersonIds = getExistingPersonIdsFromDb(validRecords);
|
||||||
|
List<CcdiBizIntermediary> actualNewRecords = new ArrayList<>();
|
||||||
|
|
||||||
|
for (CcdiBizIntermediary record : validRecords) {
|
||||||
|
if (actualExistingPersonIds.contains(record.getPersonId())) {
|
||||||
|
// 记录到失败列表
|
||||||
|
failures.add(createFailureVO(record, "该证件号码已存在"));
|
||||||
|
} else {
|
||||||
|
actualNewRecords.add(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量插入新记录
|
||||||
|
if (!actualNewRecords.isEmpty()) {
|
||||||
|
saveBatch(actualNewRecords, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 批量更新已有数据(先删除再插入)
|
// 4. 保存失败记录到Redis
|
||||||
if (!updateRecords.isEmpty() && isUpdateSupport) {
|
|
||||||
// 先批量删除已存在的记录
|
|
||||||
List<String> personIds = updateRecords.stream()
|
|
||||||
.map(CcdiBizIntermediary::getPersonId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
LambdaQueryWrapper<CcdiBizIntermediary> deleteWrapper = new LambdaQueryWrapper<>();
|
|
||||||
deleteWrapper.in(CcdiBizIntermediary::getPersonId, personIds);
|
|
||||||
intermediaryMapper.delete(deleteWrapper);
|
|
||||||
|
|
||||||
// 批量插入更新后的数据
|
|
||||||
intermediaryMapper.insertBatch(updateRecords);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 保存失败记录到Redis
|
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
String failuresKey = "import:intermediary:" + taskId + ":failures";
|
String failuresKey = "import:intermediary:" + taskId + ":failures";
|
||||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 更新最终状态
|
// 5. 更新最终状态
|
||||||
ImportResult result = new ImportResult();
|
ImportResult result = new ImportResult();
|
||||||
result.setTotalCount(excelList.size());
|
result.setTotalCount(excelList.size());
|
||||||
result.setSuccessCount(newRecords.size() + updateRecords.size());
|
result.setSuccessCount(validRecords.size() - failures.size());
|
||||||
result.setFailureCount(failures.size());
|
result.setFailureCount(failures.size());
|
||||||
|
|
||||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||||
@@ -183,6 +176,58 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存(使用ON DUPLICATE KEY UPDATE)
|
||||||
|
*/
|
||||||
|
private void saveBatchWithUpsert(List<CcdiBizIntermediary> list, int batchSize) {
|
||||||
|
for (int i = 0; i < list.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, list.size());
|
||||||
|
List<CcdiBizIntermediary> subList = list.subList(i, end);
|
||||||
|
intermediaryMapper.importPersonBatch(subList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据库获取已存在的证件号
|
||||||
|
*/
|
||||||
|
private Set<String> getExistingPersonIdsFromDb(List<CcdiBizIntermediary> records) {
|
||||||
|
List<String> personIds = records.stream()
|
||||||
|
.map(CcdiBizIntermediary::getPersonId)
|
||||||
|
.filter(StringUtils::isNotEmpty)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (personIds.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
|
||||||
|
List<CcdiBizIntermediary> existing = intermediaryMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
return existing.stream()
|
||||||
|
.map(CcdiBizIntermediary::getPersonId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建失败记录VO
|
||||||
|
*/
|
||||||
|
private IntermediaryPersonImportFailureVO createFailureVO(CcdiIntermediaryPersonExcel excel, String errorMsg) {
|
||||||
|
IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO();
|
||||||
|
BeanUtils.copyProperties(excel, failure);
|
||||||
|
failure.setErrorMessage(errorMsg);
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建失败记录VO(重载方法)
|
||||||
|
*/
|
||||||
|
private IntermediaryPersonImportFailureVO createFailureVO(CcdiBizIntermediary record, String errorMsg) {
|
||||||
|
CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
|
||||||
|
BeanUtils.copyProperties(record, excel);
|
||||||
|
return createFailureVO(excel, errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量保存
|
* 批量保存
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user