diff --git a/docs/howto.md b/docs/howto.md index 61d8d6e82d..211bbde1ad 100644 --- a/docs/howto.md +++ b/docs/howto.md @@ -912,6 +912,19 @@ scripts can act as "poor man's migrations" - inserts that fail mean that the data is already there, so there would be no need to prevent the application from running, for instance. +### Spring Batch + +If you are using Spring Batch then it comes pre-packaged with SQL +initialization scripts for most popular database platforms. Spring +Boot will detect your database type, and execute those scripts by +default, and in this case will switch the fail fast setting to false +(errors are logged but do not prevent the application from +starting). This is because the scripts are known to be reliable and +generally do not contain bugs, so errors are ignorable, and ignoring +them makes the scripts idempotent. You can switch off the +initialization explicitly using +`spring.batch.initializer.enabled=false`. + ### Higher Level Migration Tools Spring Boot works fine with higher level migration tools @@ -921,6 +934,31 @@ Flyway because it is easier on the eyes, and it isn't very common to need platform independence: usually only one or at most couple of platforms is needed. +## Execute Spring Batch Jobs on Startup + +Spring Batch autoconfiguration is enabled by adding +`@EnableBatchProcessing` (from Spring Batch) somewhere in your +context. + +By default it executes *all* `Jobs` in the application context on +startup (see +[JobLauncherCommandLineRunner](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java) +for details). You can narrow down to a specific job or jobs by +specifying `spring.batch.job.names` (comma separated job name +patterns). + +If the application context includes a `JobRegistry` then +the jobs in `spring.batch.job.names` are looked up in the regsitry +instead of bein autowired from the context. This is a common pattern +with more complex systems where multiple jobs are defined in child +contexts and registered centrally. + +See +[BatchAutoConfiguration](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java) +and +[@EnableBatchProcessing](https://github.com/spring-projects/spring-batch/blob/master/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java) +for more details. + ## Discover Built-in Options for External Properties diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java index 9d4bb67332..a729776ff7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java @@ -23,17 +23,20 @@ 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.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; 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.launch.JobLauncher; 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.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationEventPublisher; @@ -63,7 +66,7 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, @Autowired(required = false) private JobRegistry jobRegistry; - + @Autowired private JobRepository jobRepository; @@ -103,14 +106,12 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, for (String jobName : jobsToRun) { try { Job job = this.jobRegistry.getJob(jobName); - JobExecution previousExecution = jobRepository.getLastJobExecution(jobName, jobParameters); - if (previousExecution == null || previousExecution.getStatus() != BatchStatus.COMPLETED) { - JobExecution execution = this.jobLauncher.run(job, jobParameters); - if (this.publisher != null) { - this.publisher.publishEvent(new JobExecutionEvent(execution)); - } + if (this.jobs.contains(job)) { + continue; } - } catch (NoSuchJobException nsje) { + execute(job, jobParameters); + } + catch (NoSuchJobException nsje) { logger.debug("No job found in registry for job name: " + jobName); continue; } @@ -118,6 +119,20 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, } } + protected void execute(Job job, JobParameters jobParameters) + throws JobExecutionAlreadyRunningException, JobRestartException, + JobInstanceAlreadyCompleteException, JobParametersInvalidException { + String jobName = job.getName(); + JobExecution previousExecution = this.jobRepository.getLastJobExecution(jobName, + jobParameters); + if (previousExecution == null || previousExecution.getStatus().isUnsuccessful()) { + JobExecution execution = this.jobLauncher.run(job, jobParameters); + if (this.publisher != null) { + this.publisher.publishEvent(new JobExecutionEvent(execution)); + } + } + } + private void executeLocalJobs(JobParameters jobParameters) throws JobExecutionException { for (Job job : this.jobs) { @@ -128,10 +143,7 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, continue; } } - JobExecution execution = this.jobLauncher.run(job, jobParameters); - if (this.publisher != null) { - this.publisher.publishEvent(new JobExecutionEvent(execution)); - } + execute(job, jobParameters); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java index 5137e8f27a..3476926c76 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java @@ -114,29 +114,30 @@ public class BatchAutoConfigurationTests { assertNotNull(this.context.getBean(JobRepository.class).getLastJobExecution( "job", new JobParameters())); } - + @Test public void testDefinesAndLaunchesNamedJob() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.batch.job.names:discreteRegisteredJob"); - this.context.register(NamedJobConfigurationWithRegisteredJob.class, BatchAutoConfiguration.class, - EmbeddedDataSourceConfiguration.class, + this.context.register(NamedJobConfigurationWithRegisteredJob.class, + BatchAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); + JobRepository repository = this.context.getBean(JobRepository.class); assertNotNull(this.context.getBean(JobLauncher.class)); this.context.getBean(JobLauncherCommandLineRunner.class).run(); - assertNotNull(this.context.getBean(JobRepository.class).getLastJobExecution( - "discreteRegisteredJob", new JobParameters())); + assertNotNull(repository.getLastJobExecution("discreteRegisteredJob", + new JobParameters())); } - + @Test public void testDefinesAndLaunchesLocalJob() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.batch.job.names:discreteLocalJob"); - this.context.register(NamedJobConfigurationWithLocalJob.class, BatchAutoConfiguration.class, - EmbeddedDataSourceConfiguration.class, + this.context.register(NamedJobConfigurationWithLocalJob.class, + BatchAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); assertNotNull(this.context.getBean(JobLauncher.class)); @@ -206,17 +207,17 @@ public class BatchAutoConfigurationTests { protected static class NamedJobConfigurationWithRegisteredJob { @Autowired private JobRegistry jobRegistry; - + @Autowired private JobRepository jobRepository; - + @Bean public JobRegistryBeanPostProcessor registryProcessor() { JobRegistryBeanPostProcessor processor = new JobRegistryBeanPostProcessor(); - processor.setJobRegistry(jobRegistry); + processor.setJobRegistry(this.jobRegistry); return processor; } - + @Bean public Job discreteJob() { AbstractJob job = new AbstractJob("discreteRegisteredJob") { @@ -241,13 +242,13 @@ public class BatchAutoConfigurationTests { return job; } } - + @EnableBatchProcessing protected static class NamedJobConfigurationWithLocalJob { - + @Autowired private JobRepository jobRepository; - + @Bean public Job discreteJob() { AbstractJob job = new AbstractJob("discreteLocalJob") { @@ -272,7 +273,7 @@ public class BatchAutoConfigurationTests { return job; } } - + @EnableBatchProcessing protected static class JobConfiguration { @Autowired