Skip to content

Commit 845f2c3

Browse files
committed
Fix last step execution retrieval in MongoStepExecutionDao
The patch is taken from #4898, credits to @quaff. Resolves #4896
1 parent 0d3832f commit 845f2c3

File tree

2 files changed

+120
-7
lines changed

2 files changed

+120
-7
lines changed

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.batch.core.repository.dao.mongodb;
1717

18-
import java.util.ArrayList;
1918
import java.util.Comparator;
2019
import java.util.List;
2120
import java.util.Optional;
@@ -110,24 +109,26 @@ public StepExecution getStepExecution(JobExecution jobExecution, long stepExecut
110109
return stepExecution != null ? this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution) : null;
111110
}
112111

112+
@Nullable
113113
@Override
114114
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
115115
// TODO optimize the query
116116
// get all step executions
117-
List<org.springframework.batch.core.repository.persistence.StepExecution> stepExecutions = new ArrayList<>();
118117
Query query = query(where("jobInstanceId").is(jobInstance.getId()));
119118
List<org.springframework.batch.core.repository.persistence.JobExecution> jobExecutions = this.mongoOperations
120119
.find(query, org.springframework.batch.core.repository.persistence.JobExecution.class,
121120
JOB_EXECUTIONS_COLLECTION_NAME);
122-
for (org.springframework.batch.core.repository.persistence.JobExecution jobExecution : jobExecutions) {
123-
stepExecutions.addAll(jobExecution.getStepExecutions());
124-
}
121+
List<org.springframework.batch.core.repository.persistence.StepExecution> stepExecutions = this.mongoOperations
122+
.find(query(where("jobExecutionId").in(jobExecutions.stream()
123+
.map(org.springframework.batch.core.repository.persistence.JobExecution::getJobExecutionId)
124+
.toList())), org.springframework.batch.core.repository.persistence.StepExecution.class,
125+
STEP_EXECUTIONS_COLLECTION_NAME);
125126
// sort step executions by creation date then id (see contract) and return the
126-
// first one
127+
// last one
127128
Optional<org.springframework.batch.core.repository.persistence.StepExecution> lastStepExecution = stepExecutions
128129
.stream()
129130
.filter(stepExecution -> stepExecution.getName().equals(stepName))
130-
.min(Comparator
131+
.max(Comparator
131132
.comparing(org.springframework.batch.core.repository.persistence.StepExecution::getCreateTime)
132133
.thenComparing(org.springframework.batch.core.repository.persistence.StepExecution::getId));
133134
if (lastStepExecution.isPresent()) {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.repository.support;
17+
18+
import java.io.IOException;
19+
import java.nio.file.Files;
20+
21+
import com.mongodb.client.MongoCollection;
22+
import org.bson.Document;
23+
import org.junit.jupiter.api.Assertions;
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.testcontainers.junit.jupiter.Testcontainers;
27+
28+
import org.springframework.batch.core.ExitStatus;
29+
import org.springframework.batch.core.job.Job;
30+
import org.springframework.batch.core.job.JobExecution;
31+
import org.springframework.batch.core.job.JobInstance;
32+
import org.springframework.batch.core.job.builder.JobBuilder;
33+
import org.springframework.batch.core.job.parameters.JobParameters;
34+
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
35+
import org.springframework.batch.core.launch.JobOperator;
36+
import org.springframework.batch.core.repository.JobRepository;
37+
import org.springframework.batch.core.step.StepExecution;
38+
import org.springframework.batch.core.step.builder.StepBuilder;
39+
import org.springframework.batch.infrastructure.repeat.RepeatStatus;
40+
import org.springframework.beans.factory.annotation.Autowired;
41+
import org.springframework.core.io.FileSystemResource;
42+
import org.springframework.core.io.Resource;
43+
import org.springframework.data.mongodb.MongoTransactionManager;
44+
import org.springframework.data.mongodb.core.MongoTemplate;
45+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
46+
47+
/**
48+
* @author Mahmoud Ben Hassine
49+
*/
50+
@Testcontainers(disabledWithoutDocker = true)
51+
@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class)
52+
public class MongoDBJobRestartIntegrationTests {
53+
54+
@Autowired
55+
private MongoTemplate mongoTemplate;
56+
57+
@BeforeEach
58+
public void setUp() throws IOException {
59+
Resource resource = new FileSystemResource(
60+
"src/main/resources/org/springframework/batch/core/schema-mongodb.jsonl");
61+
Files.lines(resource.getFilePath()).forEach(line -> mongoTemplate.executeCommand(line));
62+
}
63+
64+
@Test
65+
void testJobExecutionRestart(@Autowired JobOperator jobOperator, @Autowired JobRepository jobRepository,
66+
@Autowired MongoTransactionManager transactionManager) throws Exception {
67+
// given
68+
Job job = new JobBuilder("job", jobRepository)
69+
.start(new StepBuilder("step", jobRepository).tasklet((contribution, chunkContext) -> {
70+
boolean shouldFail = (boolean) chunkContext.getStepContext().getJobParameters().get("shouldfail");
71+
if (shouldFail) {
72+
throw new RuntimeException("Step failed");
73+
}
74+
return RepeatStatus.FINISHED;
75+
}, transactionManager).build())
76+
.build();
77+
JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo")
78+
// shouldfail is non identifying => no effect on job instance identity
79+
.addJobParameter("shouldfail", true, Boolean.class, false)
80+
.toJobParameters();
81+
82+
// First run - expected to fail
83+
JobExecution jobExecution1 = jobOperator.start(job, jobParameters);
84+
Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution1.getExitStatus().getExitCode());
85+
86+
// Second run - expected to fail again
87+
JobExecution jobExecution2 = jobOperator.start(job, jobParameters);
88+
Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution2.getExitStatus().getExitCode());
89+
90+
// Third run - expected to succeed
91+
jobParameters = new JobParametersBuilder().addString("name", "foo")
92+
.addJobParameter("shouldfail", false, Boolean.class, false)
93+
.toJobParameters();
94+
JobExecution jobExecution3 = jobOperator.start(job, jobParameters);
95+
Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution3.getExitStatus());
96+
97+
MongoCollection<Document> jobInstancesCollection = mongoTemplate.getCollection("BATCH_JOB_INSTANCE");
98+
MongoCollection<Document> jobExecutionsCollection = mongoTemplate.getCollection("BATCH_JOB_EXECUTION");
99+
MongoCollection<Document> stepExecutionsCollection = mongoTemplate.getCollection("BATCH_STEP_EXECUTION");
100+
101+
Assertions.assertEquals(1, jobInstancesCollection.countDocuments());
102+
Assertions.assertEquals(3, jobExecutionsCollection.countDocuments());
103+
Assertions.assertEquals(3, stepExecutionsCollection.countDocuments());
104+
105+
JobInstance jobInstance = jobRepository.getLastJobInstance("job");
106+
Assertions.assertNotNull(jobInstance);
107+
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, "step");
108+
Assertions.assertNotNull(lastStepExecution);
109+
Assertions.assertEquals(3, lastStepExecution.getId());
110+
}
111+
112+
}

0 commit comments

Comments
 (0)