java springboot 文件导入,判断第一列的值是否有重复

在 Spring Boot 中处理文件导入并校验第一列是否重复,推荐使用 阿里巴巴 EasyExcel (内存占用低、API 简洁)。下面提供两种实现方式:流式监听法(推荐,适合大文件)全量内存法(适合中小文件),并附完整示例。

1. 引入依赖

复制代码
复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.3</version>
</dependency>

2. 定义接收实体类

复制代码
复制代码
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ImportRowDTO {
    // index = 0 表示读取第一列(A列)
    @ExcelProperty(index = 0)
    private String firstColumnValue;
}

✅ 方案一:流式监听校验(推荐,内存友好)

逐行读取,边读边判断,不会将整个文件加载到内存,适合万行以上数据。

复制代码
复制代码
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.ReadListener;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Slf4j
public class FirstColumnCheckListener implements ReadListener<ImportRowDTO> {

    private final Set<String> seenValues = new HashSet<>();
    private final List<String> duplicateRecords = new ArrayList<>();
    private int currentRow = 0;

    @Override
    public void invoke(ImportRowDTO data, AnalysisContext context) {
        currentRow = context.readRowHolder().getRowIndex() + 1; // Excel 行号从 1 开始(EasyExcel 默认跳过表头)
        String value = data.getFirstColumnValue();

        if (value == null) return; // 跳过空值(可根据业务调整)

        String trimmed = value.trim();
        // 如需忽略大小写:String trimmed = value.trim().toLowerCase();

        if (!seenValues.add(trimmed)) {
            duplicateRecords.add("第 " + currentRow + " 行: " + trimmed);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("第一列重复校验完成,共发现 {} 处重复", duplicateRecords.size());
    }

    public List<String> getDuplicateRecords() {
        return duplicateRecords;
    }
}

Service 层调用

复制代码
复制代码
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.excel.EasyExcel;
import java.io.IOException;
import java.util.List;

@Service
public class FileImportService {

    public void validateFirstColumn(MultipartFile file) throws IOException {
        FirstColumnCheckListener listener = new FirstColumnCheckListener();
        
        EasyExcel.read(file.getInputStream(), ImportRowDTO.class, listener)
                .sheet()
                .doRead(); // 触发流式读取

        List<String> duplicates = listener.getDuplicateRecords();
        if (!duplicates.isEmpty()) {
            // 抛出业务异常或返回错误列表
            throw new IllegalArgumentException("第一列存在重复值:\n" + String.join("\n", duplicates));
        }
    }
}

✅ 方案二:全量读取后校验(代码更简短,适合小文件)

复制代码
复制代码
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.alibaba.excel.EasyExcel;

public void validateFirstColumnSimple(MultipartFile file) throws IOException {
    List<ImportRowDTO> list = EasyExcel.read(file.getInputStream(), ImportRowDTO.class, null)
            .sheet()
            .doReadSync(); // 同步读取全部数据到内存

    // 过滤空值 → 按值分组统计 → 筛选出现次数 >1 的项
    List<String> duplicates = list.stream()
            .map(ImportRowDTO::getFirstColumnValue)
            .filter(Objects::nonNull)
            .map(String::trim)
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
            .entrySet().stream()
            .filter(entry -> entry.getValue() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

    if (!duplicates.isEmpty()) {
        throw new IllegalArgumentException("第一列重复值: " + String.join(", ", duplicates));
    }
}

📌 Controller 示例

复制代码
复制代码
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/import")
public class ImportController {

    private final FileImportService fileImportService;

    public ImportController(FileImportService fileImportService) {
        this.fileImportService = fileImportService;
    }

    @PostMapping("/check-duplicate")
    public String checkFirstColumn(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) return "文件不能为空";
        try {
            fileImportService.validateFirstColumn(file);
            return "✅ 第一列无重复,校验通过";
        } catch (Exception e) {
            return "❌ " + e.getMessage();
        }
    }
}

💡 关键注意事项

场景 处理建议
空值是否算重复 当前代码跳过 null,如需将空字符串视为重复,改为 if (value == null) value = "";
忽略大小写 String trimmed = value.trim().toLowerCase();
去除首尾空格 已内置 .trim(),防止 "ABC ""ABC" 被误判为不同值
CSV 文件支持 EasyExcel 默认支持 .csv,无需修改代码
性能对比 流式监听:O(n) 时间,O(k) 内存(k 为不重复值数量)<br>全量读取:O(n) 时间,O(n) 内存
返回结构化错误 建议封装 List<RowError> 返回前端,便于高亮展示错误行

如需将校验结果与后续入库逻辑结合,可在 doAfterAllAnalysed 中触发事务方法,或使用 @Transactional 包裹导入流程。如有具体业务场景(如:重复时跳过/覆盖/报错),可进一步调整逻辑。

以下代码会读取excel表头吗 List<ImportRowDTO> list = EasyExcel.read(file.getInputStream(), ImportRowDTO.class, null) .sheet() .doReadSync();

默认情况下,不会读取表头。

📖 原理说明

EasyExcel 默认配置 headRowNumber = 1,即认为 Excel 的第一行是表头 。在执行 doReadSync() 时,它会自动跳过第 1 行,只从第 2 行开始解析数据并返回到 List 中。

🔧 如果你想包含表头一起读取

需要显式设置 .headRowNumber(0)

复制代码
复制代码
List<ImportRowDTO> list = EasyExcel.read(file.getInputStream(), ImportRowDTO.class, null)
        .sheet()
        .headRowNumber(0) // 0 表示不跳过任何行,第一行也会作为普通数据读取
        .doReadSync();

⚠️ 重要注意事项

场景 影响
保持默认(跳过表头) ✅ 推荐。表头不会被解析进 List,业务代码无需额外过滤,直接按数据行处理即可。
设置 headRowNumber(0) 表头内容(如 "用户ID""姓名")会被当作普通字符串解析到 ImportRowDTO 的字段中。由于你的第一列是 String 类型,通常不会报错,但需在后续逻辑中手动剔除或标记。
字段映射方式 如果你的实体类使用 @ExcelProperty(index = 0),无论是否包含表头,都会按列索引映射;如果使用 @ExcelProperty("列名"),EasyExcel 会先匹配表头名称,再按规则解析数据行。

💡 针对你的"第一列重复校验"场景

建议保持默认(不读取表头) 。因为表头本身通常不参与业务数据校验,跳过表头能避免将 "第一列标题" 误判为重复值或引发类型转换问题。

如需进一步控制读取起始行或处理复杂表头(如多行表头),可参考 EasyExcel 官方文档的 headRowNumberuseDefaultListener 配置。

相关推荐
扬帆破浪1 小时前
免费开源AI软件.桌面单机版,可移动的AI知识库,察元 AI桌面版:本地离线知识库的妥协与收益 老电脑跑察元AI的可行边界
人工智能·windows·开源·电脑·知识图谱
~|Bernard|1 小时前
四,go语言中GMP调度模型
java·前端·golang
Tisfy1 小时前
LeetCode 2553.分割数组中数字的数位:模拟(maybe+翻转)——java也O(1)
java·数学·算法·leetcode·题解·模拟·取模
阿丰资源1 小时前
基于Springboot+mysql的在线兼职平台(附源码)
spring boot·后端·mysql
怪祝浙1 小时前
从简单项目入手Java(学生系统)V6(Web版本 Spring Boot3 MySQL Vue3 MyBatis)
java·spring boot·mysql
吴声子夜歌1 小时前
Java——Integer与二进制算法
java·算法
风味蘑菇干2 小时前
继承 + static + final 综合应用
java·开发语言
Lyrig~2 小时前
ClaudeCode-cli Windows终端安装,并配置Openrouter链接
windows
li星野2 小时前
二分查找六题通关:从标准模板到旋转数组(Python + C++)
java·c++·python