java – How to trigger a scheduled Spring Batch Job?

java – How to trigger a scheduled Spring Batch Job?

I would approach it in a way, that scheduled job runs always, 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 a controller:

@RestController
class HelloController {

    private final ScheduledJob scheduledJob;

    // constructor

    @GetMapping(/launch)
    void toggle() {
        scheduledJob.toggle();
    }

}

In first you are defining the job:

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

In second you are initiating the execution of this job:

@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 launchTime paramter is introduced: by default spring batch is preventing launching the job with same parameter values.

While your schedule is quite tight – every 5 seconds you should be aware of concurrency. Or if you want to be assured that at each and every moment only 1 instance of the job is executed you can configure custom single threaded job launcher:

@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 job launcher during launch time.

@Autowired
@Qualifier(fancyJobLauncher)
private JobLauncher jobLauncher;

With this your job instances will be executed one by one (but this doesnt limits parallel execution of steps inside of your job).

java – How to trigger a scheduled Spring Batch Job?

In this solution you will be able to schedule and unschedule pre defined jobs using http requests. In this example we will create a daily, weekly and an oneTime Job. The application is using 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 have to create an AutowiringSpringBeanJobFactory class extends SpringBeanJobFactory.

  • Subclass of {@link AdaptableJobFactory} that also supports
    Spring-style * dependency injection on bean properties. This is
    essentially the direct * equivalent of Springs {@link QuartzJobBean}
    in the shape of a Quartz * {@link org.quartz.spi.JobFactory}. * *

    Applies scheduler context, job data map and trigger data map
    entries * as bean property values. If no matching bean property is
    found, the entry * is by default simply ignored. This is analogous to
    QuartzJobBeans behavior.

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 to configure the quartz configuration. In this config we need to create a

  • SchedulerFactoryBean where we set global config and the application context,
  • JobDetailFactoryBean where we set our job, the jobGroup and the 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 config is done we are now able to create our jobs where the business logic will be placed. For that we have to create a class which 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 is now ready to get scheduled. We want to schedule this job from outside via a http request. In this example we have a controller where we can send the jobname and the cron expression to schedule the 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 is that we will send a post request with a JobModel as @RequestBody. JobModel is a simple Pojo with two attributes name and cronExpression both Strings.

In this method we have to create the bean instances which we have configured previously in our config class. First create JobDetail with Quartz JobDetail.class, the name of your job, the name of the group and the Class which should be scheduled (in this case DailyJob.class). After that we have to create the Trigger with Quartz Trigger.class, the cronExpression and the group name.

After both beans are created we need to schedule the job now. So we have autowired Quartz Scheduler to schedule the job. After that the job is enabled and ready to do its job.

So lets test the stuff. Start the application and send a post request to /job/create/daily:

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

Here we say that the job should run every minute (just to see that everything works). 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 the scheduled jobs:

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

To delete a job you can create endpoints, too. For example:

@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 are free to create many different endpoints to get informations about currently running jobs, how often jobs were running, reschedule jobs and so on. Important is just, that your jobname and the jobgroup( in our case MyDailyJob) are reusable. Those information are needed to create the jobKey.

P.S.: Just to show the other mappings for the other jobs:

@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 application is on github

Leave a Reply

Your email address will not be published. Required fields are marked *