You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring项目中Excel与数据库互导时多字段去重实现方案问询

看起来你正在搭建一个Excel和数据库双向同步的Spring服务,关于数据去重和导入导出的核心逻辑,我整理了一套实用的方案,你可以参考下:

核心解决方案与代码实现

一、先预加载数据库的唯一标识数据

要实现Excel数据的重复校验,首先得把数据库中已存在的ID、手机号、用户名这三个关键字段的数据捞出来,存到Set集合里做快速校验。

1. 数据库查询方法(Repository层)

在你的ContactRepository里添加一个查询方法,专门获取这三个字段的组合:

@Repository
public interface ContactRepository extends JpaRepository<Contact, Long> {
    @Query("SELECT c.id, c.phone, c.username FROM Contact c")
    List<Object[]> findIdPhoneUsernameCombination();
}

2. 封装唯一键集合

在业务层写一个方法,把查询到的数据拼接成唯一键(用特殊分隔符避免不同字段组合冲突):

@Autowired
private ContactRepository contactRepository;

// 加载数据库中已存在的唯一标识组合
private Set<String> getExistingUniqueKeys() {
    List<Object[]> existingCombinations = contactRepository.findIdPhoneUsernameCombination();
    Set<String> uniqueKeySet = new HashSet<>();
    
    for (Object[] parts : existingCombinations) {
        String id = Objects.toString(parts[0], "");
        String phone = Objects.toString(parts[1], "");
        String username = Objects.toString(parts[2], "");
        // 用|做分隔符,确保不同字段的组合不会重复
        String uniqueKey = String.join("|", id, phone, username);
        uniqueKeySet.add(uniqueKey);
    }
    return uniqueKeySet;
}

二、Excel读取阶段的双重去重校验

修改你现有的read方法,同时实现Excel内部数据去重和与数据库数据重复校验:

@Override
public List<Contact> read(String filePath) throws IOException {
    List<Contact> validContactList = new ArrayList<>();
    Set<String> dbUniqueKeys = getExistingUniqueKeys();
    Set<String> excelInnerKeys = new HashSet<>(); // 用于记录Excel内已出现的组合

    try (FileInputStream inputStream = new FileInputStream(new File(filePath));
         XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) { // XLS格式用HSSFWorkbook
        XSSFSheet sheet = workbook.getSheetAt(0); // 默认读取第一个工作表
        // 跳过表头,从第2行开始(索引为1)
        for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
            XSSFRow row = sheet.getRow(rowNum);
            if (row == null) continue;

            // 读取Excel行数据,封装成Contact对象(根据你的实际列顺序调整)
            Contact contact = new Contact();
            contact.setId(Objects.toString(row.getCell(0), ""));
            contact.setPhone(Objects.toString(row.getCell(1), ""));
            contact.setUsername(Objects.toString(row.getCell(2), ""));
            // 其他字段读取...

            // 生成当前数据的唯一键
            String currentKey = String.join("|", contact.getId(), contact.getPhone(), contact.getUsername());

            // 双重校验:Excel内无重复 + 数据库中不存在
            if (!excelInnerKeys.contains(currentKey) && !dbUniqueKeys.contains(currentKey)) {
                excelInnerKeys.add(currentKey);
                validContactList.add(contact);
            } else {
                // 可添加日志记录重复数据信息
                log.warn("跳过重复数据:ID={}, 手机号={}, 用户名={}", contact.getId(), contact.getPhone(), contact.getUsername());
            }
        }
    } catch (IOException e) {
        log.error("读取Excel文件失败", e);
        throw e;
    }
    return validContactList;
}

注意:这里用Objects.toString()处理空值,避免NullPointerException;如果字段是数字类型,要做对应的类型转换(比如row.getCell(0).getNumericCellValue()后转字符串)。

三、数据库导入的兜底去重逻辑

为了避免并发场景下的重复插入(比如多个请求同时导入相同数据),必须加上数据库层面的约束和代码层面的冲突处理:

1. 数据库添加联合唯一索引

给contact表添加ID、手机号、用户名的联合唯一索引:

ALTER TABLE contact ADD UNIQUE INDEX idx_id_phone_username (id, phone, username);

这样即使代码校验漏了,数据库会直接抛出唯一约束异常,阻止重复数据插入。

2. 批量插入时处理冲突

用原生SQL实现批量插入,结合数据库的冲突处理语法(MySQL用ON DUPLICATE KEY UPDATE,PostgreSQL用ON CONFLICT DO NOTHING):

@Autowired
private EntityManager entityManager;

public void batchImportContacts(List<Contact> contactList) {
    String sql = "INSERT INTO contact (id, phone, username, email, create_time) " +
                 "VALUES (?, ?, ?, ?, NOW()) " +
                 "ON DUPLICATE KEY UPDATE id = id"; // 重复时不做更新,也可根据需求更新其他字段
    
    int batchSize = 50; // 批量大小按需调整
    for (int i = 0; i < contactList.size(); i++) {
        Contact contact = contactList.get(i);
        entityManager.createNativeQuery(sql)
                .setParameter(1, contact.getId())
                .setParameter(2, contact.getPhone())
                .setParameter(3, contact.getUsername())
                .setParameter(4, contact.getEmail())
                .executeUpdate();
        
        if (i % batchSize == 0) {
            entityManager.flush();
            entityManager.clear();
        }
    }
    entityManager.flush();
    entityManager.clear();
}

四、数据库导出Excel的基础实现

如果是从数据库导出数据到Excel,直接查询后用Apache POI写入即可(数据库已通过唯一索引保证数据不重复):

public void exportContacts(List<Contact> contactList, String exportPath) throws IOException {
    try (XSSFWorkbook workbook = new XSSFWorkbook();
         FileOutputStream outputStream = new FileOutputStream(new File(exportPath))) {
        XSSFSheet sheet = workbook.createSheet("联系人数据");
        
        // 写入表头
        XSSFRow headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("ID");
        headerRow.createCell(1).setCellValue("手机号");
        headerRow.createCell(2).setCellValue("用户名");
        headerRow.createCell(3).setCellValue("邮箱");
        
        // 写入数据行
        int rowNum = 1;
        for (Contact contact : contactList) {
            XSSFRow row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(contact.getId());
            row.createCell(1).setCellValue(contact.getPhone());
            row.createCell(2).setCellValue(contact.getUsername());
            row.createCell(3).setCellValue(contact.getEmail());
        }
        
        workbook.write(outputStream);
    } catch (IOException e) {
        log.error("导出Excel失败", e);
        throw e;
    }
}

内容的提问来源于stack exchange,提问作者andu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:34:15