Skip to content

Commit 10f9b66

Browse files
Wordbefmbenhassine
authored andcommitted
Optimize findRunningJobExecutions to use batch query
Replace N+1 MongoDB queries with a single batch query using the $in operator in MongoJobExecutionDao.findRunningJobExecutions(). Resolves #5356 Signed-off-by: Wordbe <seonghojin3@gmail.com>
1 parent 1021416 commit 10f9b66

2 files changed

Lines changed: 51 additions & 13 deletions

File tree

spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobExecutionDao.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024-2025 the original author or authors.
2+
* Copyright 2024-2026 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,10 @@
2323
import java.util.Date;
2424
import java.util.HashSet;
2525
import java.util.List;
26+
import java.util.Map;
2627
import java.util.Set;
28+
import java.util.function.Function;
29+
import java.util.stream.Collectors;
2730

2831
import org.springframework.batch.core.job.JobExecution;
2932
import org.springframework.batch.core.job.JobInstance;
@@ -116,18 +119,19 @@ public JobExecution getLastJobExecution(JobInstance jobInstance) {
116119
@Override
117120
public Set<JobExecution> findRunningJobExecutions(String jobName) {
118121
List<JobInstance> jobInstances = this.jobInstanceDao.findJobInstancesByName(jobName);
119-
Set<JobExecution> runningJobExecutions = new HashSet<>();
120-
for (JobInstance jobInstance : jobInstances) {
121-
Query query = query(
122-
where("jobInstanceId").is(jobInstance.getId()).and("status").in("STARTING", "STARTED", "STOPPING"));
123-
this.mongoOperations
124-
.find(query, org.springframework.batch.core.repository.persistence.JobExecution.class,
125-
JOB_EXECUTIONS_COLLECTION_NAME)
126-
.stream()
127-
.map(jobExecution -> convert(jobExecution, jobInstance))
128-
.forEach(runningJobExecutions::add);
122+
if (jobInstances.isEmpty()) {
123+
return Collections.emptySet();
129124
}
130-
return runningJobExecutions;
125+
Map<Long, JobInstance> jobInstanceMap = jobInstances.stream()
126+
.collect(Collectors.toMap(JobInstance::getId, Function.identity()));
127+
Query query = query(
128+
where("jobInstanceId").in(jobInstanceMap.keySet()).and("status").in("STARTING", "STARTED", "STOPPING"));
129+
return this.mongoOperations
130+
.find(query, org.springframework.batch.core.repository.persistence.JobExecution.class,
131+
JOB_EXECUTIONS_COLLECTION_NAME)
132+
.stream()
133+
.map(jobExecution -> convert(jobExecution, jobInstanceMap.get(jobExecution.getJobInstanceId())))
134+
.collect(Collectors.toSet());
131135
}
132136

133137
@Override

spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoJobExecutionDaoIntegrationTests.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2025 the original author or authors.
2+
* Copyright 2008-2026 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@
3939
import java.util.List;
4040
import java.util.Set;
4141
import java.util.concurrent.TimeUnit;
42+
import java.util.stream.Collectors;
4243

4344
import static org.junit.jupiter.api.Assertions.*;
4445

@@ -241,6 +242,39 @@ void testFindRunningExecutions(@Autowired StepExecutionDao stepExecutionDao) {
241242

242243
}
243244

245+
@Test
246+
void testFindRunningExecutionsAcrossMultipleInstances(@Autowired JobInstanceDao jobInstanceDao) {
247+
String jobName = "multiInstanceJob";
248+
JobInstance instance1 = jobInstanceDao.createJobInstance(jobName,
249+
new JobParametersBuilder().addLong("id", 1L).toJobParameters());
250+
JobInstance instance2 = jobInstanceDao.createJobInstance(jobName,
251+
new JobParametersBuilder().addLong("id", 2L).toJobParameters());
252+
LocalDateTime now = LocalDateTime.now();
253+
254+
JobExecution running1 = dao.createJobExecution(instance1, jobParameters);
255+
running1.setStatus(BatchStatus.STARTED);
256+
running1.setStartTime(now);
257+
dao.updateJobExecution(running1);
258+
259+
JobExecution completed = dao.createJobExecution(instance2, jobParameters);
260+
completed.setStatus(BatchStatus.COMPLETED);
261+
completed.setStartTime(now);
262+
completed.setEndTime(now);
263+
dao.updateJobExecution(completed);
264+
265+
JobExecution running2 = dao.createJobExecution(instance2, jobParameters);
266+
running2.setStatus(BatchStatus.STARTED);
267+
running2.setStartTime(now);
268+
dao.updateJobExecution(running2);
269+
270+
Set<JobExecution> result = dao.findRunningJobExecutions(jobName);
271+
272+
assertEquals(2, result.size());
273+
Set<Long> ids = result.stream().map(JobExecution::getId).collect(Collectors.toSet());
274+
assertTrue(ids.contains(running1.getId()));
275+
assertTrue(ids.contains(running2.getId()));
276+
}
277+
244278
/**
245279
* Check the execution is returned
246280
*/

0 commit comments

Comments
 (0)