diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunnerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunnerTests.java index 385b86b3ac9..bd93b9a05b4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunnerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunnerTests.java @@ -16,31 +16,39 @@ package org.springframework.boot.autoconfigure.batch; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import java.util.List; + +import javax.sql.DataSource; + import org.junit.jupiter.api.Test; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.BatchConfigurer; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.builder.SimpleJobBuilder; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.support.RunIdIncrementer; -import org.springframework.batch.core.launch.support.SimpleJobLauncher; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; -import org.springframework.batch.support.transaction.ResourcelessTransactionManager; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.io.ResourceLoader; import org.springframework.transaction.PlatformTransactionManager; import static org.assertj.core.api.Assertions.assertThat; @@ -57,138 +65,101 @@ import static org.assertj.core.api.Assertions.fail; */ class JobLauncherApplicationRunnerTests { - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - private JobLauncherApplicationRunner runner; - - private JobExplorer jobExplorer; - - private JobBuilderFactory jobs; - - private StepBuilderFactory steps; - - private Job job; - - private Step step; - - @BeforeEach - void init() { - this.context.register(BatchConfiguration.class); - this.context.refresh(); - JobRepository jobRepository = this.context.getBean(JobRepository.class); - JobLauncher jobLauncher = this.context.getBean(JobLauncher.class); - this.jobs = new JobBuilderFactory(jobRepository); - PlatformTransactionManager transactionManager = this.context.getBean(PlatformTransactionManager.class); - this.steps = new StepBuilderFactory(jobRepository, transactionManager); - Tasklet tasklet = (contribution, chunkContext) -> null; - this.step = this.steps.get("step").tasklet(tasklet).build(); - this.job = this.jobs.get("job").start(this.step).build(); - this.jobExplorer = this.context.getBean(JobExplorer.class); - this.runner = new JobLauncherApplicationRunner(jobLauncher, this.jobExplorer, jobRepository); - this.context.getBean(BatchConfiguration.class).clear(); - } - - @AfterEach - void closeContext() { - this.context.close(); - } - - @Test - void basicExecution() throws Exception { - this.runner.execute(this.job, new JobParameters()); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); - this.runner.execute(this.job, new JobParametersBuilder().addLong("id", 1L).toJobParameters()); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2); - } + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class)) + .withUserConfiguration(BatchConfiguration.class) + .withPropertyValues("spring.datasource.initialization-mode=never"); @Test - void incrementExistingExecution() throws Exception { - this.job = this.jobs.get("job").start(this.step).incrementer(new RunIdIncrementer()).build(); - this.runner.execute(this.job, new JobParameters()); - this.runner.execute(this.job, new JobParameters()); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2); + void basicExecution() { + this.contextRunner.run((context) -> { + JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context); + jobLauncherContext.executeJob(new JobParameters()); + assertThat(jobLauncherContext.jobInstances()).hasSize(1); + jobLauncherContext.executeJob(new JobParametersBuilder().addLong("id", 1L).toJobParameters()); + assertThat(jobLauncherContext.jobInstances()).hasSize(2); + }); } @Test - void retryFailedExecution() throws Exception { - this.job = this.jobs.get("job").start(this.steps.get("step").tasklet(throwingTasklet()).build()) - .incrementer(new RunIdIncrementer()).build(); - this.runner.execute(this.job, new JobParameters()); - this.runner.execute(this.job, new JobParametersBuilder().addLong("run.id", 1L).toJobParameters()); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); + void incrementExistingExecution() { + 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()); + assertThat(jobLauncherContext.jobInstances()).hasSize(2); + }); } @Test - void runDifferentInstances() throws Exception { - this.job = this.jobs.get("job").start(this.steps.get("step").tasklet(throwingTasklet()).build()).build(); - // start a job instance - JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo").toJobParameters(); - this.runner.execute(this.job, jobParameters); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); - // start a different job instance - JobParameters otherJobParameters = new JobParametersBuilder().addString("name", "bar").toJobParameters(); - this.runner.execute(this.job, otherJobParameters); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2); + void retryFailedExecution() { + this.contextRunner.run((context) -> { + JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context); + Job job = jobLauncherContext.jobBuilder() + .start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet()).build()) + .incrementer(new RunIdIncrementer()).build(); + jobLauncherContext.runner.execute(job, new JobParameters()); + jobLauncherContext.runner.execute(job, new JobParametersBuilder().addLong("run.id", 1L).toJobParameters()); + assertThat(jobLauncherContext.jobInstances()).hasSize(1); + }); } @Test - void retryFailedExecutionOnNonRestartableJob() throws Exception { - this.job = this.jobs.get("job").preventRestart() - .start(this.steps.get("step").tasklet(throwingTasklet()).build()).incrementer(new RunIdIncrementer()) - .build(); - this.runner.execute(this.job, new JobParameters()); - this.runner.execute(this.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(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(2); - assertThatExceptionOfType(JobRestartException.class).isThrownBy(() -> { - // try to re-run a failed execution - this.runner.execute(this.job, new JobParametersBuilder().addLong("run.id", 1L).toJobParameters()); - fail("expected JobRestartException"); - }).withMessageContaining("JobInstance already exists and is not restartable"); + void runDifferentInstances() { + this.contextRunner.run((context) -> { + JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context); + Job job = jobLauncherContext.jobBuilder() + .start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet()).build()).build(); + // start a job instance + JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo").toJobParameters(); + jobLauncherContext.runner.execute(job, jobParameters); + assertThat(jobLauncherContext.jobInstances()).hasSize(1); + // start a different job instance + JobParameters otherJobParameters = new JobParametersBuilder().addString("name", "bar").toJobParameters(); + jobLauncherContext.runner.execute(job, otherJobParameters); + assertThat(jobLauncherContext.jobInstances()).hasSize(2); + }); } @Test - void retryFailedExecutionWithNonIdentifyingParameters() throws Exception { - this.job = this.jobs.get("job").start(this.steps.get("step").tasklet(throwingTasklet()).build()) - .incrementer(new RunIdIncrementer()).build(); - JobParameters jobParameters = new JobParametersBuilder().addLong("id", 1L, false).addLong("foo", 2L, false) - .toJobParameters(); - this.runner.execute(this.job, jobParameters); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); - // try to re-run a failed execution with non identifying parameters - this.runner.execute(this.job, new JobParametersBuilder(jobParameters).addLong("run.id", 1L).toJobParameters()); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); + void retryFailedExecutionOnNonRestartableJob() { + this.contextRunner.run((context) -> { + JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context); + Job job = jobLauncherContext.jobBuilder().preventRestart() + .start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet()).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); + assertThatExceptionOfType(JobRestartException.class).isThrownBy(() -> { + // try to re-run a failed execution + jobLauncherContext.runner.execute(job, + new JobParametersBuilder().addLong("run.id", 1L).toJobParameters()); + fail("expected JobRestartException"); + }).withMessageContaining("JobInstance already exists and is not restartable"); + }); } @Test - void retryFailedExecutionWithDifferentNonIdentifyingParametersFromPreviousExecution() throws Exception { - this.job = this.jobs.get("job").start(this.steps.get("step").tasklet(throwingTasklet()).build()) - .incrementer(new RunIdIncrementer()).build(); - JobParameters jobParameters = new JobParametersBuilder().addLong("id", 1L, false).addLong("foo", 2L, false) - .toJobParameters(); - this.runner.execute(this.job, jobParameters); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); - // try to re-run a failed execution with non identifying parameters - this.runner.execute(this.job, new JobParametersBuilder().addLong("run.id", 1L).addLong("id", 2L, false) - .addLong("foo", 3L, false).toJobParameters()); - assertThat(this.jobExplorer.getJobInstances("job", 0, 100)).hasSize(1); - JobInstance jobInstance = this.jobExplorer.getJobInstance(0L); - assertThat(this.jobExplorer.getJobExecutions(jobInstance)).hasSize(2); - // first execution - JobExecution firstJobExecution = this.jobExplorer.getJobExecution(0L); - JobParameters parameters = firstJobExecution.getJobParameters(); - assertThat(parameters.getLong("run.id")).isEqualTo(1L); - assertThat(parameters.getLong("id")).isEqualTo(1L); - assertThat(parameters.getLong("foo")).isEqualTo(2L); - // second execution - JobExecution secondJobExecution = this.jobExplorer.getJobExecution(1L); - parameters = secondJobExecution.getJobParameters(); - // identifying parameters should be the same as previous execution - assertThat(parameters.getLong("run.id")).isEqualTo(1L); - // non-identifying parameters should be the newly specified ones - assertThat(parameters.getLong("id")).isEqualTo(2L); - assertThat(parameters.getLong("foo")).isEqualTo(3L); + void retryFailedExecutionWithNonIdentifyingParameters() { + this.contextRunner.run((context) -> { + JobLauncherApplicationRunnerContext jobLauncherContext = new JobLauncherApplicationRunnerContext(context); + Job job = jobLauncherContext.jobBuilder() + .start(jobLauncherContext.stepBuilder().tasklet(throwingTasklet()).build()) + .incrementer(new RunIdIncrementer()).build(); + JobParameters jobParameters = new JobParametersBuilder().addLong("id", 1L, false).addLong("foo", 2L, false) + .toJobParameters(); + jobLauncherContext.runner.execute(job, jobParameters); + assertThat(jobLauncherContext.jobInstances()).hasSize(1); + // try to re-run a failed execution with non identifying parameters + jobLauncherContext.runner.execute(job, + new JobParametersBuilder(jobParameters).addLong("run.id", 1L).toJobParameters()); + assertThat(jobLauncherContext.jobInstances()).hasSize(1); + }); } private Tasklet throwingTasklet() { @@ -197,48 +168,67 @@ class JobLauncherApplicationRunnerTests { }; } - @Configuration(proxyBeanMethods = false) - @EnableBatchProcessing - @SuppressWarnings("deprecation") - static class BatchConfiguration implements BatchConfigurer { + static class JobLauncherApplicationRunnerContext { + + private final JobLauncherApplicationRunner runner; + + private final JobExplorer jobExplorer; - private ResourcelessTransactionManager transactionManager = new ResourcelessTransactionManager(); + private final JobBuilderFactory jobs; - private JobRepository jobRepository; + private final StepBuilderFactory steps; - private org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean jobRepositoryFactory = new org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean( - this.transactionManager); + private final Job job; - BatchConfiguration() throws Exception { - this.jobRepository = this.jobRepositoryFactory.getObject(); + private final Step step; + + JobLauncherApplicationRunnerContext(ApplicationContext context) { + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobRepository jobRepository = context.getBean(JobRepository.class); + this.jobs = new JobBuilderFactory(jobRepository); + this.steps = new StepBuilderFactory(jobRepository, context.getBean(PlatformTransactionManager.class)); + this.step = this.steps.get("step").tasklet((contribution, chunkContext) -> null).build(); + this.job = this.jobs.get("job").start(this.step).build(); + this.jobExplorer = context.getBean(JobExplorer.class); + this.runner = new JobLauncherApplicationRunner(jobLauncher, this.jobExplorer, jobRepository); + } + + List jobInstances() { + return this.jobExplorer.getJobInstances("job", 0, 100); } - void clear() { - this.jobRepositoryFactory.clear(); + void executeJob(JobParameters jobParameters) throws JobExecutionException { + this.runner.execute(this.job, jobParameters); } - @Override - public JobRepository getJobRepository() { - return this.jobRepository; + JobBuilder jobBuilder() { + return this.jobs.get("job"); } - @Override - public PlatformTransactionManager getTransactionManager() { - return this.transactionManager; + StepBuilder stepBuilder() { + return this.steps.get("step"); } - @Override - public JobLauncher getJobLauncher() { - SimpleJobLauncher launcher = new SimpleJobLauncher(); - launcher.setJobRepository(this.jobRepository); - launcher.setTaskExecutor(new SyncTaskExecutor()); - return launcher; + SimpleJobBuilder configureJob() { + return this.jobs.get("job").start(this.step); + } + + } + + @Configuration(proxyBeanMethods = false) + @EnableBatchProcessing + static class BatchConfiguration extends BasicBatchConfigurer { + + private final DataSource dataSource; + + protected BatchConfiguration(DataSource dataSource) { + super(new BatchProperties(), dataSource, new TransactionManagerCustomizers(null)); + this.dataSource = dataSource; } - @Override - public JobExplorer getJobExplorer() throws Exception { - return new org.springframework.batch.core.explore.support.MapJobExplorerFactoryBean( - this.jobRepositoryFactory).getObject(); + @Bean + BatchDataSourceInitializer batchDataSourceInitializer(ResourceLoader resourceLoader) { + return new BatchDataSourceInitializer(this.dataSource, resourceLoader, new BatchProperties()); } }