如何用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,适合逻辑复杂的场景。
关键注意点
- SQL排序:建议在查询SQL末尾添加
ORDER BY JOB_INSTANCE_ID,确保同一Job的行连续,避免分组时的冗余判断 - 字段映射一致性:原POJO中
JobDetails的字段是jobStatus,但期望JSON是jobExecutionStatus,可以用@JsonProperty注解调整:@JsonProperty("jobExecutionStatus") private String jobStatus; - 拼写错误修复:原代码中
STEP_SATUS是拼写错误,要改成STEP_STATUS,否则会导致Step的status字段值为null或抛出SQL异常
内容的提问来源于stack exchange,提问作者CtrlAltElite
相关产品推荐
相关产品推荐

