Skip to content

Commit 82d9c15

Browse files
hpoettkerfmbenhassine
authored andcommitted
Fix job execution retrieval by id for MongoDB
Resolves #4722 Signed-off-by: Mahmoud Ben Hassine <[email protected]>
1 parent ba2202a commit 82d9c15

File tree

7 files changed

+396
-93
lines changed

7 files changed

+396
-93
lines changed

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

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

1818
import java.util.Collection;
19-
import java.util.Map;
2019

2120
import org.springframework.batch.core.JobExecution;
2221
import org.springframework.batch.core.StepExecution;
@@ -46,8 +45,9 @@ public MongoExecutionContextDao(MongoOperations mongoOperations) {
4645

4746
@Override
4847
public ExecutionContext getExecutionContext(JobExecution jobExecution) {
49-
org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findById(
50-
jobExecution.getId(), org.springframework.batch.core.repository.persistence.JobExecution.class,
48+
Query query = query(where("jobExecutionId").is(jobExecution.getId()));
49+
org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findOne(
50+
query, org.springframework.batch.core.repository.persistence.JobExecution.class,
5151
JOB_EXECUTIONS_COLLECTION_NAME);
5252
if (execution == null) {
5353
return new ExecutionContext();
@@ -57,8 +57,9 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) {
5757

5858
@Override
5959
public ExecutionContext getExecutionContext(StepExecution stepExecution) {
60-
org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findById(
61-
stepExecution.getId(), org.springframework.batch.core.repository.persistence.StepExecution.class,
60+
Query query = query(where("stepExecutionId").is(stepExecution.getId()));
61+
org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findOne(
62+
query, org.springframework.batch.core.repository.persistence.StepExecution.class,
6263
STEP_EXECUTIONS_COLLECTION_NAME);
6364
if (execution == null) {
6465
return new ExecutionContext();
@@ -69,7 +70,7 @@ public ExecutionContext getExecutionContext(StepExecution stepExecution) {
6970
@Override
7071
public void saveExecutionContext(JobExecution jobExecution) {
7172
ExecutionContext executionContext = jobExecution.getExecutionContext();
72-
Query query = query(where("_id").is(jobExecution.getId()));
73+
Query query = query(where("jobExecutionId").is(jobExecution.getId()));
7374

7475
Update update = Update.update("executionContext",
7576
new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(),
@@ -82,7 +83,7 @@ public void saveExecutionContext(JobExecution jobExecution) {
8283
@Override
8384
public void saveExecutionContext(StepExecution stepExecution) {
8485
ExecutionContext executionContext = stepExecution.getExecutionContext();
85-
Query query = query(where("_id").is(stepExecution.getId()));
86+
Query query = query(where("stepExecutionId").is(stepExecution.getId()));
8687

8788
Update update = Update.update("executionContext",
8889
new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(),

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,17 @@ public Set<JobExecution> findRunningJobExecutions(String jobName) {
126126

127127
@Override
128128
public JobExecution getJobExecution(Long executionId) {
129-
org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findById(
130-
executionId, org.springframework.batch.core.repository.persistence.JobExecution.class,
129+
Query jobExecutionQuery = query(where("jobExecutionId").is(executionId));
130+
org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findOne(
131+
jobExecutionQuery, org.springframework.batch.core.repository.persistence.JobExecution.class,
131132
JOB_EXECUTIONS_COLLECTION_NAME);
132133
if (jobExecution == null) {
133134
return null;
134135
}
135-
org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findById(
136-
jobExecution.getJobInstanceId(),
137-
org.springframework.batch.core.repository.persistence.JobInstance.class, JOB_INSTANCES_COLLECTION_NAME);
136+
Query jobInstanceQuery = query(where("jobInstanceId").is(jobExecution.getJobInstanceId()));
137+
org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findOne(
138+
jobInstanceQuery, org.springframework.batch.core.repository.persistence.JobInstance.class,
139+
JOB_INSTANCES_COLLECTION_NAME);
138140
return this.jobExecutionConverter.toJobExecution(jobExecution,
139141
this.jobInstanceConverter.toJobInstance(jobInstance));
140142
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ public void updateStepExecution(StepExecution stepExecution) {
8989

9090
@Override
9191
public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) {
92+
Query query = query(where("stepExecutionId").is(stepExecutionId));
9293
org.springframework.batch.core.repository.persistence.StepExecution stepExecution = this.mongoOperations
93-
.findById(stepExecutionId, org.springframework.batch.core.repository.persistence.StepExecution.class,
94+
.findOne(query, org.springframework.batch.core.repository.persistence.StepExecution.class,
9495
STEP_EXECUTIONS_COLLECTION_NAME);
9596
return stepExecution != null ? this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution) : null;
9697
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2024 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 com.mongodb.client.MongoClient;
19+
import com.mongodb.client.MongoClients;
20+
import org.springframework.batch.core.Job;
21+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
22+
import org.springframework.batch.core.explore.JobExplorer;
23+
import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean;
24+
import org.springframework.batch.core.job.builder.JobBuilder;
25+
import org.springframework.batch.core.repository.JobRepository;
26+
import org.springframework.batch.core.step.builder.StepBuilder;
27+
import org.springframework.batch.repeat.RepeatStatus;
28+
import org.springframework.beans.factory.annotation.Value;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.data.mongodb.MongoDatabaseFactory;
32+
import org.springframework.data.mongodb.MongoTransactionManager;
33+
import org.springframework.data.mongodb.core.MongoTemplate;
34+
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
35+
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
36+
37+
/**
38+
* @author Mahmoud Ben Hassine
39+
*/
40+
@Configuration
41+
@EnableBatchProcessing
42+
class MongoDBIntegrationTestConfiguration {
43+
44+
@Bean
45+
public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager)
46+
throws Exception {
47+
MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean();
48+
jobRepositoryFactoryBean.setMongoOperations(mongoTemplate);
49+
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
50+
jobRepositoryFactoryBean.afterPropertiesSet();
51+
return jobRepositoryFactoryBean.getObject();
52+
}
53+
54+
@Bean
55+
public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager)
56+
throws Exception {
57+
MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean();
58+
jobExplorerFactoryBean.setMongoOperations(mongoTemplate);
59+
jobExplorerFactoryBean.setTransactionManager(transactionManager);
60+
jobExplorerFactoryBean.afterPropertiesSet();
61+
return jobExplorerFactoryBean.getObject();
62+
}
63+
64+
@Bean
65+
public MongoDatabaseFactory mongoDatabaseFactory(@Value("${mongo.connectionString}") String connectionString) {
66+
MongoClient mongoClient = MongoClients.create(connectionString);
67+
return new SimpleMongoClientDatabaseFactory(mongoClient, "test");
68+
}
69+
70+
@Bean
71+
public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) {
72+
MongoTemplate template = new MongoTemplate(mongoDatabaseFactory);
73+
MappingMongoConverter converter = (MappingMongoConverter) template.getConverter();
74+
converter.setMapKeyDotReplacement(".");
75+
return template;
76+
}
77+
78+
@Bean
79+
public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) {
80+
MongoTransactionManager mongoTransactionManager = new MongoTransactionManager();
81+
mongoTransactionManager.setDatabaseFactory(mongoDatabaseFactory);
82+
mongoTransactionManager.afterPropertiesSet();
83+
return mongoTransactionManager;
84+
}
85+
86+
@Bean
87+
public Job job(JobRepository jobRepository, MongoTransactionManager transactionManager) {
88+
return new JobBuilder("job", jobRepository)
89+
.start(new StepBuilder("step1", jobRepository)
90+
.tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager)
91+
.build())
92+
.next(new StepBuilder("step2", jobRepository)
93+
.tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager)
94+
.build())
95+
.build();
96+
}
97+
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2024 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.time.LocalDateTime;
19+
import java.util.Map;
20+
21+
import org.bson.Document;
22+
import org.junit.jupiter.api.BeforeAll;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.batch.core.Job;
25+
import org.springframework.batch.core.JobExecution;
26+
import org.springframework.batch.core.JobParameters;
27+
import org.springframework.batch.core.JobParametersBuilder;
28+
import org.springframework.batch.core.StepExecution;
29+
import org.springframework.batch.core.explore.JobExplorer;
30+
import org.springframework.batch.core.launch.JobLauncher;
31+
import org.springframework.beans.factory.annotation.Autowired;
32+
import org.springframework.data.mongodb.core.MongoTemplate;
33+
import org.springframework.test.context.DynamicPropertyRegistry;
34+
import org.springframework.test.context.DynamicPropertySource;
35+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
36+
import org.testcontainers.containers.MongoDBContainer;
37+
import org.testcontainers.junit.jupiter.Container;
38+
import org.testcontainers.junit.jupiter.Testcontainers;
39+
import org.testcontainers.utility.DockerImageName;
40+
41+
import static org.junit.jupiter.api.Assertions.assertEquals;
42+
import static org.junit.jupiter.api.Assertions.assertFalse;
43+
import static org.junit.jupiter.api.Assertions.assertNotNull;
44+
45+
/**
46+
* @author Henning Pöttker
47+
*/
48+
@Testcontainers(disabledWithoutDocker = true)
49+
@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class)
50+
public class MongoDBJobExplorerIntegrationTests {
51+
52+
private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1");
53+
54+
@Container
55+
public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE);
56+
57+
@DynamicPropertySource
58+
static void setMongoDbConnectionString(DynamicPropertyRegistry registry) {
59+
registry.add("mongo.connectionString", mongodb::getConnectionString);
60+
}
61+
62+
@BeforeAll
63+
static void setUp(@Autowired MongoTemplate mongoTemplate) {
64+
mongoTemplate.createCollection("BATCH_JOB_INSTANCE");
65+
mongoTemplate.createCollection("BATCH_JOB_EXECUTION");
66+
mongoTemplate.createCollection("BATCH_STEP_EXECUTION");
67+
mongoTemplate.createCollection("BATCH_SEQUENCES");
68+
mongoTemplate.getCollection("BATCH_SEQUENCES")
69+
.insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L)));
70+
mongoTemplate.getCollection("BATCH_SEQUENCES")
71+
.insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L)));
72+
mongoTemplate.getCollection("BATCH_SEQUENCES")
73+
.insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L)));
74+
}
75+
76+
@Test
77+
void testGetJobExecutionById(@Autowired JobLauncher jobLauncher, @Autowired Job job,
78+
@Autowired JobExplorer jobExplorer) throws Exception {
79+
// given
80+
JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetJobExecutionById")
81+
.addLocalDateTime("runtime", LocalDateTime.now())
82+
.toJobParameters();
83+
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
84+
85+
// when
86+
JobExecution actual = jobExplorer.getJobExecution(jobExecution.getId());
87+
88+
// then
89+
assertNotNull(actual);
90+
assertNotNull(actual.getJobInstance());
91+
assertEquals(jobExecution.getJobId(), actual.getJobId());
92+
assertFalse(actual.getExecutionContext().isEmpty());
93+
}
94+
95+
@Test
96+
void testGetStepExecutionByIds(@Autowired JobLauncher jobLauncher, @Autowired Job job,
97+
@Autowired JobExplorer jobExplorer) throws Exception {
98+
// given
99+
JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetStepExecutionByIds")
100+
.addLocalDateTime("runtime", LocalDateTime.now())
101+
.toJobParameters();
102+
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
103+
StepExecution stepExecution = jobExecution.getStepExecutions().stream().findFirst().orElseThrow();
104+
105+
// when
106+
StepExecution actual = jobExplorer.getStepExecution(jobExecution.getId(), stepExecution.getId());
107+
108+
// then
109+
assertNotNull(actual);
110+
assertEquals(stepExecution.getId(), actual.getId());
111+
assertFalse(actual.getExecutionContext().isEmpty());
112+
}
113+
114+
}

0 commit comments

Comments
 (0)