How to run a scheduled Spring batch job?

I want to be able to start my work with a REST controller, and then when the task starts, it should start on a schedule until I stop it again using REST.

So this is my controller:

@RestController public class LauncherController { @Autowired JobLauncher jobLauncher; @Autowired Job job; @RequestMapping("/launch") public String launch() throws Exception { ... jobLauncher.run(job, jobParameters); } 

This is part of Batch conf:

 @Configuration @EnableBatchProcessing @EnableScheduling public class BatchConfiguration { @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; @Scheduled(cron = "0/5 * * * * ?") @Bean public Job job() { return jobBuilderFactory.get("job") .incrementer(new RunIdIncrementer()) .flow(step1()) .end() .build(); } @Bean public Step step1() { return stepBuilderFactory.get("step1") .<Person, Person> chunk(10) .reader(reader()) .processor(processor()) .writer(writer()) .build(); } 

I also set the spring.batch.job.enabled = false property, since I don't want jobs to start as soon as the Spring boot application starts.

Now I can call my Rest api lauch, and the work is done, but only once. The scheduler does not work. And I couldn't figure it out exactly where I should define my @Scheduled Annotation ..

+5
source share
4 answers

I would approach it so that the scheduled task is always executed, but it does something only when the flag is set to true:

 @Component class ScheduledJob { private final AtomicBoolean enabled = new AtomicBoolean(false); @Scheduled(fixedRate = 1000) void execute() { if (enabled.get()) { // run spring batch here. } } void toggle() { enabled.set(!enabled.get()); } } 

and controller:

 @RestController class HelloController { private final ScheduledJob scheduledJob; // constructor @GetMapping("/launch") void toggle() { scheduledJob.toggle(); } } 
+5
source

First, you define the task:

 @Bean @Qualifier("fancyScheduledJob") public Job job() { return jobBuilderFactory.get("job") .incrementer(new RunIdIncrementer()) .flow(step1()) .end() .build(); } 

Secondly, you start the execution of this task:

 @Autowired @Qualifier(value = "fancyScheduledJob") private Job job; @Autowired private JobLauncher jobLauncher; @Scheduled(cron = "0/5 * * * * ?") public void launch() throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobInstanceAlreadyExistsException, NoSuchJobException { jobLauncher.run(job, JobParametersBuilder() .addLong("launchTime", System.currentTimeMillis()) .toJobParameters()) } 

Also note that the "startTime" parameter is entered: by default, the spring package prevents the task from starting with the same parameter values.

So far, your schedule is pretty tight - every 5 seconds you should know concurrency. Or if you want to be sure that only one instance of the task is executed at any given time, you can configure a custom single-shot launch trigger:

 @Bean(name = "fancyJobExecutorPool") public TaskExecutor singleThreadedJobExecutorPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(1); executor.setMaxPoolSize(1); executor.setQueueCapacity(100500); executor.setThreadNamePrefix("fancy-job-batch-"); return executor; } @Bean(name = "fancyJobLauncher") public JobLauncher singleThreadedJobLauncher(JobRepository jobRepository) { SimpleJobLauncher sjl = new SimpleJobLauncher(); sjl.setJobRepository(jobRepository); sjl.setTaskExecutor(singleThreadedJobExecutorPool()); return sjl; } 

And use this single-threaded launch trigger during startup.

 @Autowired @Qualifier("fancyJobLauncher") private JobLauncher jobLauncher; 

At the same time, your task instances will be executed one after another (but this does not limit the parallel execution of steps within your task).

+3
source

In this solution, you will be able to schedule and send ahead of schedule tasks using HTTP requests. In this example, we will create a daily, weekly, and one-year assignment. The application uses Quartz .

 <!--Quartz Scheduler --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> 

First we need to create the AutowiringSpringBeanJobFactory extends SpringBeanJobFactory .

  • Subclass {@link AdaptableJobFactory}, which also supports Spring-style * dependency injection in bean properties. This is essentially the direct * equivalent of Spring {@link QuartzJobBean} in the form of quartz * {@link org.quartz.spi.JobFactory}. * * *

    Applies the scheduler context, job data map, and entries * trigger data map as values โ€‹โ€‹of bean properties. If the beanfound property is appropriate, the * entry is simply ignored by default. This is similar to QuartzJobBean.

 public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { beanFactory = applicationContext.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } } 

The second part is the configuration of quartz. In this configuration, we need to create

  • SchedulerFactoryBean , where we set the global configuration and application context,
  • JobDetailFactoryBean , where we set our job, work group and class,

  • CronTriggerFactoryBean , where we set the cron expression.

QuartzConfig.class

 @Configuration public class QuartzConfig { @Autowired ApplicationContext context; @Bean public SchedulerFactoryBean quartzScheduler(){ SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean(); quartzScheduler.setOverwriteExistingJobs(true); quartzScheduler.setSchedulerName("job-scheduler"); AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(context); quartzScheduler.setJobFactory(jobFactory); return quartzScheduler; } @Bean @Scope(value = "prototype") public JobDetailFactoryBean getJobBean(String jobName, String jobGroup, Class<?> clazz){ JobDetailFactoryBean bean = new JobDetailFactoryBean(); bean.setJobClass(clazz); bean.setGroup(jobGroup); bean.setName(jobName); return bean; } @Bean @Scope(value = "prototype") public CronTriggerFactoryBean getCronTriggerBean(String cronExpression, String triggerGroup){ CronTriggerFactoryBean bean = new CronTriggerFactoryBean(); bean.setCronExpression(cronExpression); bean.setGroup(triggerGroup); return bean; } } 

So, after the setup is complete, we can now create our jobs, where the business logic will be placed. To do this, we need to create a class that implements Job .

 @Component public class DailyJob implements Job{ @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Daily Job runs!"); } } 

The DailyJob class DailyJob now ready for planning. We want to plan this work from the outside using an HTTP request. In this example, we have a controller where we can send the job name and cron expression to schedule DailyJob .

 @Controller public class JobController { @Autowired private Scheduler scheduler; @Autowired private ApplicationContext context;; @ResponseBody @RequestMapping(value = "/job/create/daily", method = RequestMethod.POST) public ResponseEntity<JobModel> dailyJob(@RequestBody JobModel jobModel) throws SchedulerException { JobDetail jobDetail = context.getBean( JobDetail.class, jobModel.getName(), "MyDailyJob", DailyJob.class); Trigger cronTrigger = context.getBean( Trigger.class, jobModel.getCronExpression(), "MyDailyJob"); scheduler.scheduleJob(jobDetail, cronTrigger); return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED); } } 

What we see here, we will send a JobModel request with JobModel as @RequestBody . JobModel is a simple Pojo with two attributes, name and cronExpression both lines.

In this method, we must create the bean instances that we preconfigured in our configuration class. First create a JobDetail with Quartz JobDetail.class , the name of your job, the name of the group, and the class to be scheduled (in this case, DailyJob.class ). After that, we need to create a trigger with quartz Trigger.class , cronExpression and the name of the group.

After creating the beans, we need to schedule work now. So, we have an autwired Quartz Scheduler to schedule work. After that, the task is on and ready to do its job.

So let's test the material. Launch the application and send a request for the message /job/create/daily :

 {"name":"Job 1", "cronExpression":"0 * * * * ?"} 

Here we say that work should be done every minute (just to see that everything is working). In your console you should see every minute Daily Job runs! .

And here are some additional things you can do. For example, get a list of scheduled tasks:

  @ResponseBody @RequestMapping("job/list") public List<String> jobList() throws SchedulerException { return scheduler.getJobGroupNames(); } 

To delete a task, you can also create endpoints. For instance:

 @ResponseBody @RequestMapping(value = "job/delete/daily", method = RequestMethod.POST) public ResponseEntity<Boolean> deleteJob(@RequestBody JobModel jobModel) throws SchedulerException { JobKey jobKey = new JobKey(jobModel.getName(), "MyDailyJob"); return new ResponseEntity<Boolean>(scheduler.deleteJob(jobKey), HttpStatus.OK); } 

You can create many different endpoints to get information about current work tasks, how often tasks were completed, transfer tasks, etc. The only important thing is that your name and workgroup (in our case, "MyDailyJob" ) are reusable. This data is required to create a jobKey.

PS: Just show other mappings for other assignments:

 @ResponseBody @RequestMapping(value = "/job/create/weekly", method = RequestMethod.POST) public ResponseEntity<JobModel> weeklyJob(@RequestBody JobModel jobModel) throws SchedulerException { JobDetail jobDetail = context.getBean(JobDetail.class, jobModel.getName(), JobGroup.WEEKLY_GROUP.name(), WeeklyJob.class); Trigger cronTrigger = context.getBean(Trigger.class, jobModel.getCronExpression(), JobGroup.WEEKLY_GROUP.name()); scheduler.scheduleJob(jobDetail, cronTrigger); return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED); } @ResponseBody @RequestMapping(value = "/job/create/oneTime", method = RequestMethod.POST) public ResponseEntity<JobModel> oneTimeJob(@RequestBody JobModel jobModel) throws SchedulerException { JobDetail jobDetail = context.getBean(JobDetail.class, jobModel.getName(), JobGroup.ONE_TIME_GROUP.name(), OneTimeJob.class); Trigger cronTrigger = context.getBean(Trigger.class, jobModel.getCronExpression(), JobGroup.ONE_TIME_GROUP.name()); scheduler.scheduleJob(jobDetail, cronTrigger); return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED); } 

The full app is on github

+2
source

@Scheduled determined by the method, not by the Bean. So create a new class that will bean

 public class BatchConfiguration { ... @Bean public Job job() { return new Job(); } 

new class:

 public class Job { @Scheduled(cron = "0/5 * * * * ?") public Job job() { return jobBuilderFactory.get("job") .incrementer(new RunIdIncrementer()) .flow(step1()) .end() .build(); } 
+1
source

Source: https://habr.com/ru/post/1272936/


All Articles