Adapt to latest changes in Spring Batch 6

See gh-46447
This commit is contained in:
Mahmoud Ben Hassine
2025-07-17 12:05:54 +02:00
committed by Stéphane Nicoll
parent 55de634172
commit 024dba4bb8
4 changed files with 33 additions and 71 deletions
@@ -26,7 +26,6 @@ import org.springframework.batch.core.configuration.support.JdbcDefaultBatchConf
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.repository.ExecutionContextSerializer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -82,9 +81,8 @@ public class BatchAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBooleanProperty(name = "spring.batch.job.enabled", matchIfMissing = true)
public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobOperator jobOperator,
JobRepository jobRepository, BatchProperties properties) {
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobOperator, jobRepository);
public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobOperator jobOperator, BatchProperties properties) {
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobOperator);
String jobName = properties.getJob().getName();
if (StringUtils.hasText(jobName)) {
runner.setJobName(jobName);
@@ -19,29 +19,23 @@ package org.springframework.boot.batch.autoconfigure;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.job.Job;
import org.springframework.batch.core.job.JobExecution;
import org.springframework.batch.core.job.JobExecutionException;
import org.springframework.batch.core.job.parameters.JobParameter;
import org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
import org.springframework.batch.core.job.parameters.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
@@ -80,8 +74,6 @@ public class JobLauncherApplicationRunner
private final JobOperator jobOperator;
private final JobRepository jobRepository;
private JobRegistry jobRegistry;
private String jobName;
@@ -95,14 +87,10 @@ public class JobLauncherApplicationRunner
/**
* Create a new {@link JobLauncherApplicationRunner}.
* @param jobOperator to launch jobs
* @param jobRepository to check if a job instance exists with the given parameters
* when running a job
*/
public JobLauncherApplicationRunner(JobOperator jobOperator, JobRepository jobRepository) {
public JobLauncherApplicationRunner(JobOperator jobOperator) {
Assert.notNull(jobOperator, "'jobOperator' must not be null");
Assert.notNull(jobRepository, "'jobRepository' must not be null");
this.jobOperator = jobOperator;
this.jobRepository = jobRepository;
}
@Override
@@ -197,46 +185,10 @@ public class JobLauncherApplicationRunner
protected void execute(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, NoSuchJobException, JobRestartException,
JobInstanceAlreadyCompleteException, JobParametersInvalidException {
JobParameters parameters = getNextJobParameters(job, jobParameters);
JobExecution execution = this.jobOperator.start(job, parameters);
JobExecution execution = this.jobOperator.start(job, jobParameters);
if (this.publisher != null) {
this.publisher.publishEvent(new JobExecutionEvent(execution));
}
}
private JobParameters getNextJobParameters(Job job, JobParameters jobParameters) {
if (this.jobRepository != null && this.jobRepository.getJobInstance(job.getName(), jobParameters) != null) {
return getNextJobParametersForExisting(job, jobParameters);
}
if (job.getJobParametersIncrementer() == null) {
return jobParameters;
}
JobParameters nextParameters = new JobParametersBuilder(jobParameters, this.jobRepository)
.getNextJobParameters(job)
.toJobParameters();
return merge(nextParameters, jobParameters);
}
private JobParameters getNextJobParametersForExisting(Job job, JobParameters jobParameters) {
JobExecution lastExecution = this.jobRepository.getLastJobExecution(job.getName(), jobParameters);
if (isStoppedOrFailed(lastExecution) && job.isRestartable()) {
JobParameters previousIdentifyingParameters = new JobParameters(
lastExecution.getJobParameters().getIdentifyingParameters());
return merge(previousIdentifyingParameters, jobParameters);
}
return jobParameters;
}
private boolean isStoppedOrFailed(JobExecution execution) {
BatchStatus status = (execution != null) ? execution.getStatus() : null;
return (status == BatchStatus.STOPPED || status == BatchStatus.FAILED);
}
private JobParameters merge(JobParameters parameters, JobParameters additionals) {
Map<String, JobParameter<?>> merged = new LinkedHashMap<>();
merged.putAll(parameters.getParameters());
merged.putAll(additionals.getParameters());
return new JobParameters(merged);
}
}
@@ -533,8 +533,7 @@ class BatchAutoConfigurationTests {
}
private JobLauncherApplicationRunner createInstance(String... registeredJobNames) {
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(mock(JobOperator.class),
mock(JobRepository.class));
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(mock(JobOperator.class));
JobRegistry jobRegistry = mock(JobRegistry.class);
given(jobRegistry.getJobNames()).willReturn(Arrays.asList(registeredJobNames));
runner.setJobRegistry(jobRegistry);
@@ -86,14 +86,15 @@ class JobLauncherApplicationRunnerTests {
this.contextRunner.run((context) -> {
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
Job job = jobLauncherContext.configureJob().incrementer(new RunIdIncrementer()).build();
jobLauncherContext.runner.execute(job, new JobParameters());
jobLauncherContext.runner.execute(job, new JobParameters());
JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo").toJobParameters();
jobLauncherContext.runner.execute(job, jobParameters);
jobLauncherContext.runner.execute(job, jobParameters);
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
});
}
@Test
void retryFailedExecution() {
void retryFailedExecutionWithIncrementer() {
this.contextRunner.run((context) -> {
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
@@ -102,7 +103,23 @@ class JobLauncherApplicationRunnerTests {
.incrementer(new RunIdIncrementer())
.build();
jobLauncherContext.runner.execute(job, new JobParameters());
jobLauncherContext.runner.execute(job, new JobParametersBuilder().addLong("run.id", 1L).toJobParameters());
jobLauncherContext.runner.execute(job, new JobParameters());
// with an incrementer, we always create a new job instance
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
});
}
@Test
void retryFailedExecutionWithoutIncrementer() {
this.contextRunner.run((context) -> {
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
Job job = jobLauncherContext.jobBuilder()
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
.build();
JobParameters jobParameters = new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
jobLauncherContext.runner.execute(job, jobParameters);
jobLauncherContext.runner.execute(job, jobParameters);
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
});
}
@@ -134,17 +151,14 @@ class JobLauncherApplicationRunnerTests {
Job job = jobLauncherContext.jobBuilder()
.preventRestart()
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
.incrementer(new RunIdIncrementer())
.build();
jobLauncherContext.runner.execute(job, new JobParameters());
jobLauncherContext.runner.execute(job, new JobParameters());
// A failed job that is not restartable does not re-use the job params of
// the last execution, but creates a new job instance when running it again.
assertThat(jobLauncherContext.jobInstances()).hasSize(2);
JobParameters jobParameters = new JobParametersBuilder()
.addString("name", "foo").toJobParameters();
jobLauncherContext.runner.execute(job, jobParameters);
assertThat(jobLauncherContext.jobInstances()).hasSize(1);
assertThatExceptionOfType(JobRestartException.class).isThrownBy(() -> {
// try to re-run a failed execution
jobLauncherContext.runner.execute(job,
new JobParametersBuilder().addLong("run.id", 1L).toJobParameters());
jobLauncherContext.runner.execute(job, jobParameters);
fail("expected JobRestartException");
}).withMessageContaining("JobInstance already exists and is not restartable");
});
@@ -157,9 +171,8 @@ class JobLauncherApplicationRunnerTests {
JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context);
Job job = jobLauncherContext.jobBuilder()
.start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet(), transactionManager).build())
.incrementer(new RunIdIncrementer())
.build();
JobParameters jobParameters = new JobParametersBuilder().addLong("id", 1L, false)
JobParameters jobParameters = new JobParametersBuilder().addLong("run.id", 1L, true)
.addLong("foo", 2L, false)
.toJobParameters();
jobLauncherContext.runner.execute(job, jobParameters);
@@ -200,7 +213,7 @@ class JobLauncherApplicationRunnerTests {
this.jobBuilder = new JobBuilder("job", jobRepository);
this.job = this.jobBuilder.start(this.step).build();
this.jobRepository = context.getBean(JobRepository.class);
this.runner = new JobLauncherApplicationRunner(jobOperator, jobRepository);
this.runner = new JobLauncherApplicationRunner(jobOperator);
}
List<JobInstance> jobInstances() {