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

Java导入CSV并持久化到数据库时的多SensorError关联问题

问题

使用Java 11 + SpringBoot 2.7.3开发REST API时,遇到CSV数据导入数据库的问题。Report类与SensorError类为单向多对多关系(仅Report知晓SensorError),实体注解如下:

@ManyToMany 
@JoinTable( 
    name = "rel_report__sensor_error", 
    joinColumns = @JoinColumn(name = "report_id"), 
    inverseJoinColumns = @JoinColumn(name = "sensor_error_id") 
) 
private Set<SensorError> sensorErrors = new HashSet<>();

编写的CSV导入代码如下:

public static List<Report> csvToReports(InputStream is, ReportRepository reportRepository,
        SensorErrorRepository sensorErrorRepository) {
    try (BufferedReader fileReader = new BufferedReader(new InputStreamReader(is, Charset.defaultCharset()));
            CSVParser csvParser = new CSVParser(fileReader,
                    CSVFormat.DEFAULT.withFirstRecordAsHeader().withIgnoreHeaderCase().withTrim());) {
        List<Report> reports = new ArrayList<>();
        Set<SensorError> sensorErrors = new HashSet<>();

        Iterable<CSVRecord> csvRecords = csvParser.getRecords();

        for (CSVRecord csvRecord : csvRecords) {

            Report report = new Report(csvRecord.get("final_state"), Instant.parse(csvRecord.get("datetime_begin")),
                    Instant.parse(csvRecord.get("datetime_end")), csvRecord.get("cutting_time"),
                    csvRecord.get("total_time"));

            if (!csvRecord.get("sensor_name").isEmpty() || !csvRecord.get("error_code").isEmpty()
                    || !csvRecord.get("error_detail").isEmpty()) {
                SensorError sensorError = new SensorError(csvRecord.get("sensor_name"), csvRecord.get("error_code"),
                        csvRecord.get("error_detail"));
                sensorErrors.add(sensorError);
            }

            report.setSensorErrors(sensorErrors);
            sensorErrorRepository.saveAll(sensorErrors);
            reports.add(report);
        }

        return reports;
    } catch (IOException e) {
        throw new RuntimeException("Failed to parse CSV file: " + e.getMessage());
    }
}

CSV文件内容:

final_state,datetime_begin,datetime_end,cutting_time,total_time,sensor_name,error_code,error_detail
TESTESENSOR,2022-09-30T18:15:00Z,2022-09-30T18:35:00Z,30min,40min, "sensor operador","operando","falha parcial","sensor navalha","erro mundial","falha total"

当前代码仅能保存Report对应的第一个SensorError信息("sensor operador","operando","falha parcial"),第二个("sensor navalha","erro mundial","falha total")无法保存。尝试过在循环内新建Set<SensorError>,但此时仅能保存第二个;将Set改为List也未解决问题。需要实现将CSV中单个Report对应的多个SensorError信息全部导入并关联。

解决方案

问题根源

  1. CSV解析逻辑缺陷:默认的CSVParser遇到同一行内的同名表头字段时,只会读取第一个值,后续的同名字段会被直接忽略,导致无法获取第二组SensorError数据。
  2. 集合作用域错误:原代码中sensorErrors集合定义在循环外部,会被所有Report实例共享,导致数据混乱;且未处理同一行内的多组错误数据。

解决步骤

1. 调整CSV格式(推荐)

如果可以修改CSV结构,建议将同一Report的多个SensorError拆分为多行,每行对应一个SensorError,共用Report的基础信息,示例:

final_state,datetime_begin,datetime_end,cutting_time,total_time,sensor_name,error_code,error_detail
TESTESENSOR,2022-09-30T18:15:00Z,2022-09-30T18:35:00Z,30min,40min,"sensor operador","operando","falha parcial"
TESTESENSOR,2022-09-30T18:15:00Z,2022-09-30T18:35:00Z,30min,40min,"sensor navalha","erro mundial","falha total"

这种格式更符合CSV规范,解析逻辑会更简单。

2. 修改代码适配现有CSV格式

如果无法修改CSV,需要手动解析同一行内的多组SensorError数据。核心思路是:

  • 为每个Report创建独立的Set<SensorError>集合(将集合初始化放在循环内部)
  • 获取CSV行的所有原始值,按每3个一组(sensor_name、error_code、error_detail)拆分,生成多个SensorError实例

修改后的代码:

public static List<Report> csvToReports(InputStream is, ReportRepository reportRepository,
        SensorErrorRepository sensorErrorRepository) {
    try (BufferedReader fileReader = new BufferedReader(new InputStreamReader(is, Charset.defaultCharset()));
            CSVParser csvParser = new CSVParser(fileReader,
                    CSVFormat.DEFAULT.withFirstRecordAsHeader().withIgnoreHeaderCase().withTrim());) {
        List<Report> reports = new ArrayList<>();
        Iterable<CSVRecord> csvRecords = csvParser.getRecords();

        for (CSVRecord csvRecord : csvRecords) {
            // 为当前Report创建独立的SensorError集合
            Set<SensorError> sensorErrors = new HashSet<>();
            
            Report report = new Report(csvRecord.get("final_state"), 
                    Instant.parse(csvRecord.get("datetime_begin")),
                    Instant.parse(csvRecord.get("datetime_end")), 
                    csvRecord.get("cutting_time"),
                    csvRecord.get("total_time"));

            // 获取CSV行中从第5个索引开始的所有值(前5个是Report的基础字段)
            List<String> errorFields = csvRecord.toList().subList(5, csvRecord.size());
            // 每3个字段组成一个SensorError
            for (int i = 0; i < errorFields.size(); i += 3) {
                if (i + 2 >= errorFields.size()) {
                    break; // 避免数组越界
                }
                String sensorName = errorFields.get(i).trim().replace("\"", "");
                String errorCode = errorFields.get(i+1).trim().replace("\"", "");
                String errorDetail = errorFields.get(i+2).trim().replace("\"", "");
                
                if (!sensorName.isEmpty() && !errorCode.isEmpty() && !errorDetail.isEmpty()) {
                    SensorError sensorError = new SensorError(sensorName, errorCode, errorDetail);
                    sensorErrors.add(sensorError);
                }
            }

            report.setSensorErrors(sensorErrors);
            sensorErrorRepository.saveAll(sensorErrors);
            reports.add(report);
        }

        return reports;
    } catch (IOException e) {
        throw new RuntimeException("Failed to parse CSV file: " + e.getMessage());
    }
}

补充说明

  • 如果使用拆分多行的CSV格式,只需要将sensorErrors的初始化移到循环内部,原有的解析逻辑即可正常工作,无需修改字段拆分部分。
  • 注意SensorError类需要正确重写equals()和hashCode()方法,否则HashSet可能会出现重复元素或者无法正确添加的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 02:28:16