Skip to content

Commit 088487b

Browse files
committed
Update graceful shutdown sample
1 parent 5722803 commit 088487b

File tree

3 files changed

+120
-19
lines changed

3 files changed

+120
-19
lines changed

spring-batch-samples/src/main/java/org/springframework/batch/samples/shutdown/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ gracefully, allowing it to complete its current processing before shutting down.
1212

1313
## Run the sample
1414

15+
### 1. Start the job
16+
1517
First, you need to start the database server of the job repository. In a terminal, run:
1618

1719
```
@@ -26,12 +28,16 @@ Get the process id from the first line of logs (needed for the stop):
2628
Process id = 73280
2729
```
2830

31+
### 2. Stop the job
32+
2933
In a second terminal, Gracefully stop the app by running:
3034

3135
```
3236
$>kill -15 73280
3337
```
3438

39+
You can otherwise run the `org.springframework.batch.samples.shutdown.StopJobExecutionApp` class in your IDE with no arguments.
40+
3541
You should see the shutdown hook being called:
3642

3743
```
@@ -50,6 +56,8 @@ $>docker exec postgres psql -U postgres -c 'select * from batch_step_execution;'
5056

5157
Both the job and the step should have a `STOPPED` status.
5258

59+
### 3. Restart the job
60+
5361
Now, you can restart the job by running the `org.springframework.batch.samples.shutdown.RestartJobExecutionApp` class in your IDE.
5462

5563
You should see that the job is restarted from the last commit point and completes successfully.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.samples.shutdown;
17+
18+
import org.springframework.batch.core.job.JobExecution;
19+
import org.springframework.batch.core.launch.JobOperator;
20+
import org.springframework.batch.core.repository.JobRepository;
21+
import org.springframework.context.ApplicationContext;
22+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
23+
24+
/**
25+
* Application to stop a job execution.
26+
*
27+
* @author Mahmoud Ben Hassine
28+
*/
29+
public class StopJobExecutionApp {
30+
31+
public static void main(String[] args) throws Exception {
32+
ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class);
33+
JobOperator jobOperator = context.getBean(JobOperator.class);
34+
JobRepository jobRepository = context.getBean(JobRepository.class);
35+
JobExecution jobExecution = jobRepository.getJobExecution(1);
36+
jobOperator.stop(jobExecution);
37+
}
38+
39+
}

spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/GracefulShutdownFunctionalTests.java

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2025 the original author or authors.
2+
* Copyright 2006-present 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.
@@ -16,19 +16,37 @@
1616

1717
package org.springframework.batch.samples.restart.stop;
1818

19+
import java.util.List;
20+
21+
import javax.sql.DataSource;
22+
1923
import org.apache.commons.logging.Log;
2024
import org.apache.commons.logging.LogFactory;
21-
import org.junit.jupiter.api.Disabled;
2225
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.extension.ExtendWith;
2327

2428
import org.springframework.batch.core.BatchStatus;
29+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
30+
import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository;
31+
import org.springframework.batch.core.job.Job;
2532
import org.springframework.batch.core.job.JobExecution;
33+
import org.springframework.batch.core.job.builder.JobBuilder;
2634
import org.springframework.batch.core.job.parameters.JobParameters;
27-
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
2835
import org.springframework.batch.core.launch.JobOperator;
29-
import org.springframework.batch.test.JobOperatorTestUtils;
36+
import org.springframework.batch.core.repository.JobRepository;
37+
import org.springframework.batch.core.step.Step;
38+
import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder;
39+
import org.springframework.batch.infrastructure.item.support.ListItemReader;
40+
import org.springframework.batch.infrastructure.item.support.ListItemWriter;
3041
import org.springframework.beans.factory.annotation.Autowired;
31-
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
42+
import org.springframework.context.annotation.Bean;
43+
import org.springframework.context.annotation.Configuration;
44+
import org.springframework.core.task.AsyncTaskExecutor;
45+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
46+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
47+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
48+
import org.springframework.jdbc.support.JdbcTransactionManager;
49+
import org.springframework.test.context.junit.jupiter.SpringExtension;
3250

3351
import static org.junit.jupiter.api.Assertions.assertEquals;
3452
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -44,26 +62,15 @@
4462
* @author Mahmoud Ben Hassine
4563
*
4664
*/
47-
@SpringJUnitConfig(locations = { "/org/springframework/batch/samples/restart/stop/stopRestartSample.xml" })
48-
@Disabled
49-
// FIXME passes in the IDE but not on the CLI - needs investigation
65+
@ExtendWith(SpringExtension.class)
5066
class GracefulShutdownFunctionalTests {
5167

5268
/** Logger */
5369
private final Log logger = LogFactory.getLog(getClass());
5470

55-
@Autowired
56-
private JobOperatorTestUtils jobOperatorTestUtils;
57-
58-
@Autowired
59-
private JobOperator jobOperator;
60-
6171
@Test
62-
void testLaunchJob() throws Exception {
63-
final JobParameters jobParameters = new JobParametersBuilder().addLong("timestamp", System.currentTimeMillis())
64-
.toJobParameters();
65-
66-
JobExecution jobExecution = jobOperatorTestUtils.startJob(jobParameters);
72+
void testStopJob(@Autowired Job job, @Autowired JobOperator jobOperator) throws Exception {
73+
JobExecution jobExecution = jobOperator.start(job, new JobParameters());
6774

6875
Thread.sleep(1000);
6976

@@ -83,4 +90,51 @@ void testLaunchJob() throws Exception {
8390
assertEquals(BatchStatus.STOPPED, jobExecution.getStatus());
8491
}
8592

93+
@Configuration
94+
@EnableBatchProcessing(taskExecutorRef = "batchTaskExecutor")
95+
@EnableJdbcJobRepository
96+
static class JobConfiguration {
97+
98+
@Bean
99+
public Job job(JobRepository jobRepository, Step step) {
100+
return new JobBuilder(jobRepository).start(step).build();
101+
}
102+
103+
@Bean
104+
public Step chunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager) {
105+
return new ChunkOrientedStepBuilder<String, String>(jobRepository, 2)
106+
.reader(new ListItemReader<>(List.of("item1", "item2", "item3", "item4", "item5")))
107+
.processor(item -> {
108+
// Simulate processing time
109+
Thread.sleep(500);
110+
return item.toUpperCase();
111+
})
112+
.writer(new ListItemWriter<>())
113+
.transactionManager(transactionManager)
114+
.build();
115+
}
116+
117+
@Bean
118+
public DataSource dataSource() {
119+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
120+
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
121+
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
122+
.generateUniqueName(true)
123+
.build();
124+
}
125+
126+
@Bean
127+
public JdbcTransactionManager transactionManager(DataSource dataSource) {
128+
return new JdbcTransactionManager(dataSource);
129+
}
130+
131+
@Bean
132+
public AsyncTaskExecutor batchTaskExecutor() {
133+
SimpleAsyncTaskExecutor asyncTaskExecutor = new SimpleAsyncTaskExecutor("batch-executor");
134+
asyncTaskExecutor.setConcurrencyLimit(1);
135+
return asyncTaskExecutor;
136+
}
137+
138+
}
139+
86140
}

0 commit comments

Comments
 (0)