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

如何用ResultSetExtractor将ResultSet映射为嵌套Java对象

优化ResultSetExtractor实现:映射嵌套Job-Step对象

需求

要把ResultSet映射成包含List<StepDetails>的JobDetails列表,一个Job实例对应多个Step条目。现有代码能运行,但逻辑绕、可读性差,需要更清晰高效的实现方案。

相关POJO定义

public class JobDetails {
    private long jobInstanceId;
    private long jobExecutionId;
    private String jobStatus;
    private List<StepDetails> steps;
    // 需包含Builder、getter/setter方法(原代码已使用Builder,默认实现)
}
public class StepDetails {
    private long stepExecutionId;
    private String stepName;
    private String stepStatus;
    // 需包含构造方法、getter/setter方法
}

期望输出格式

[
    {
        "jobInstanceId": 1,
        "jobExecutionId": 1,
        "jobExecutionStatus": "COMPLETED",
        "steps": [
            {
                "stepExecutionId" : 1,
                "stepName" : "automatedStep0",
                "stepStatus" : "COMPLETED"
            }
        ]
    }
]

当前实现(可运行但不够简洁)

public List<JobDetails> fetchJobs() {

    MapSqlParameterSource params = new MapSqlParameterSource();

    return jdbcTemplate.query(query, params , rs -> {

        List<JobDetails> jobList = new ArrayList<>();
        List <StepDetails> stepList = new ArrayList<>();
        JobDetails jobDetails=JobDetails.builder().build();
        long prevInstanceId=0;

        while(rs.next()) {
            
            long currentInstanceId = rs.getLong("JOB_INSTANCE_ID");
            boolean isNewInstance = currentInstanceId != prevInstanceId;
            prevInstanceId = currentInstanceId;

            if(!rs.isFirst() && (isNewInstance || rs.isLast())) {
                jobDetails.setSteps(stepList);
                jobList.add(jobDetails);
            }

            if(rs.isFirst() || isNewInstance) {
                jobDetails = JobDetails.builder()
                                        .jobInstanceId(rs.getLong("JOB_INSTANCE_ID"))
                                        .jobExecutionId(rs.getLong("JOB_EXECUTION_ID"))
                                        .jobExecutionStatus(rs.getString("JOB_STATUS"))
                                        .build();
                stepList = new ArrayList<>();
            }

            StepDetails step = new StepDetails(rs.getLong("STEP_EXECUTION_ID"), rs.getString("STEP_NAME"), rs.getString("STEP_SATUS"));
            stepList.add(step);
        }
        return jobList;
    });
}

优化方案

方案1:用Map分组简化逻辑(推荐)

核心思路是用Map<Long, JobDetails>按jobInstanceId缓存已创建的Job对象,遍历ResultSet时直接找到对应Job并添加Step,避免复杂的边界判断。

public List<JobDetails> fetchJobs() {
    MapSqlParameterSource params = new MapSqlParameterSource();

    return jdbcTemplate.query(query, params, rs -> {
        // 用LinkedHashMap保证结果顺序与ResultSet遍历顺序一致
        Map<Long, JobDetails> jobCache = new LinkedHashMap<>();

        while (rs.next()) {
            long jobInstanceId = rs.getLong("JOB_INSTANCE_ID");
            
            // 不存在则创建新的JobDetails,同时初始化steps列表
            JobDetails currentJob = jobCache.computeIfAbsent(jobInstanceId, id -> 
                JobDetails.builder()
                    .jobInstanceId(id)
                    .jobExecutionId(rs.getLong("JOB_EXECUTION_ID"))
                    .jobStatus(rs.getString("JOB_STATUS"))
                    .steps(new ArrayList<>())
                    .build()
            );

            // 创建Step并添加到当前Job的steps中
            StepDetails step = new StepDetails(
                rs.getLong("STEP_EXECUTION_ID"),
                rs.getString("STEP_NAME"),
                rs.getString("STEP_STATUS") // 修复原代码拼写错误:STEP_SATUS → STEP_STATUS
            );
            currentJob.getSteps().add(step);
        }

        // 把缓存中的Job对象转成List返回
        return new ArrayList<>(jobCache.values());
    });
}

优点

  • 逻辑直白:没有rs.isFirst()、rs.isLast()这类容易出错的边界判断
  • 代码简洁:利用computeIfAbsent一行完成“查找/创建”逻辑
  • 性能高效:仅遍历一次ResultSet,内存操作少
  • 顺序可控:LinkedHashMap保证结果顺序与查询结果一致

方案2:拆分RowMapper与内存分组(可选)

如果想把“行映射”和“分组合并”逻辑拆分得更清晰,可以先把每一行映射成临时对象,再用Stream分组合并。

// 临时类:存储单一行的Job和Step信息
private static class JobStepPair {
    private JobDetails job;
    private StepDetails step;
    // getter/setter方法省略
}

public List<JobDetails> fetchJobs() {
    MapSqlParameterSource params = new MapSqlParameterSource();

    // 第一步:把ResultSet每一行映射成JobStepPair
    List<JobStepPair> rowPairs = jdbcTemplate.query(query, params, (rs, rowNum) -> {
        JobDetails job = JobDetails.builder()
            .jobInstanceId(rs.getLong("JOB_INSTANCE_ID"))
            .jobExecutionId(rs.getLong("JOB_EXECUTION_ID"))
            .jobStatus(rs.getString("JOB_STATUS"))
            .build();
        
        StepDetails step = new StepDetails(
            rs.getLong("STEP_EXECUTION_ID"),
            rs.getString("STEP_NAME"),
            rs.getString("STEP_STATUS")
        );

        JobStepPair pair = new JobStepPair();
        pair.job = job;
        pair.step = step;
        return pair;
    });

    // 第二步:按jobInstanceId分组,合并Steps到对应Job中
    return rowPairs.stream()
        .collect(Collectors.groupingBy(
            pair -> pair.job.getJobInstanceId(),
            LinkedHashMap::new, // 保留原始顺序
            Collectors.toList()
        ))
        .entrySet().stream()
        .map(entry -> {
            // 取同ID的第一个Job作为基准(同ID的Job信息一致)
            JobDetails mergedJob = entry.getValue().get(0).job;
            // 收集所有Step到Job的steps列表
            mergedJob.setSteps(entry.getValue().stream()
                .map(JobStepPair::getStep)
                .collect(Collectors.toList())
            );
            return mergedJob;
        })
        .collect(Collectors.toList());
}

适用场景

  • 当Row映射逻辑复杂,需要单独拆分时
  • 后续需要对临时数据做更多扩展处理时

注意

这种方式需要遍历两次(ResultSet一次,内存列表一次),性能略低于方案1,适合逻辑复杂的场景。

关键注意点

  1. SQL排序:建议在查询SQL末尾添加ORDER BY JOB_INSTANCE_ID,确保同一Job的行连续,避免分组时的冗余判断
  2. 字段映射一致性:原POJO中JobDetails的字段是jobStatus,但期望JSON是jobExecutionStatus,可以用@JsonProperty注解调整:
    @JsonProperty("jobExecutionStatus")
    private String jobStatus;
    
  3. 拼写错误修复:原代码中STEP_SATUS是拼写错误,要改成STEP_STATUS,否则会导致Step的status字段值为null或抛出SQL异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 14:50:13