web-dev-qa-db-ja.com

スケジュールされたSpring Batchジョブをトリガーするには?

RESTコントローラでジョブを開始できるようにしたいので、ジョブが開始されると、RESTで再度停止するまで、スケジュールに従って実行する必要があります。

これが私のコントローラーです:

@RestController
public class LauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

    @RequestMapping("/launch")
    public String launch() throws Exception {
             ...
            jobLauncher.run(job, jobParameters);
    }

これは、バッチ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();
    }

Spring Boot Appが起動するとすぐにジョブを実行したくないので、spring.batch.job.enabled = falseプロパティも設定しました。

これで、Rest api lauchを呼び出すことができ、ジョブは1回だけ実行されます。スケジューラが機能しません。そして、@ Scheduled Annotationを正確に定義する必要がある場所を把握できませんでした。

10
akcasoy

スケジュールされたジョブは常に実行されるという方法でアプローチしますが、フラグが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());
    }

}

およびコントローラー:

@RestController
class HelloController {

    private final ScheduledJob scheduledJob;

    // constructor

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

}
15

最初に、ジョブを定義しています:

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

次に、このジョブの実行を開始します。

@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())
}

また、「launchTime」パラメーターが導入されていることに注意してください。デフォルトでは、Springバッチは同じパラメーター値でジョブを起動できません。

あなたのスケジュールは非常にタイトです-5秒ごとに並行性に注意する必要があります。または、毎回ジョブのインスタンスが1つだけ実行されるようにしたい場合は、カスタムシングルスレッドジョブランチャーを構成できます。

@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;
}

そして、起動時にこのシングルスレッドジョブランチャーを使用します。

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

これにより、ジョブインスタンスは1つずつ実行されます(ただし、これはジョブ内のステップの並列実行を制限しません)。

6
Ilya Dyoshin

このソリューションでは、httpリクエストを使用して、事前定義されたジョブをスケジュールおよびスケジュール解除できます。この例では、毎日、毎週、およびワンタイムジョブを作成します。アプリケーションは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>

まず、AutowiringSpringBeanJobFactoryクラスを拡張してSpringBeanJobFactoryを拡張する必要があります。

  • BeanプロパティへのSpringスタイル*依存性注入もサポートする{@link AdaptableJobFactory}のサブクラス。これは本質的に、Quartz * {@link org.quartz.spi.JobFactory}の形をしたSpringの{@link QuartzJobBean}に直接相当するものです。 * *

    スケジューラコンテキスト、ジョブデータマップ、およびトリガーデータマップエントリをBeanプロパティ値として適用します。一致するBeanプロパティが見つからない場合、エントリ*はデフォルトで単に無視されます。これは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;
    }
}

2番目の部分は、クォーツ構成を構成することです。この構成では、作成する必要があります

  • SchedulerFactoryBeanここで、グローバル構成とアプリケーションコンテキストを設定します。
  • JobDetailFactoryBeanジョブ、jobGroup、およびクラスを設定した場所、

  • CronTriggerFactoryBean cron式を設定します。

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;
    }
}

そのため、構成が完了したら、ビジネスロジックを配置するジョブを作成できるようになります。そのためには、Jobを実装するクラスを作成する必要があります。

@Component
public class DailyJob implements Job{

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

これで、DailyJobクラスをスケジュールする準備ができました。 httpリクエストを介して外部からこのジョブをスケジュールします。この例では、dailyJobをスケジュールするためにジョブ名とcron式を送信できるコントローラーがあります。

@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);
    }
}

ここでわかるのは、JobModel@RequestBodyとして投稿リクエストを送信することです。 JobModelは、2つの属性nameおよびcronExpression両方の文字列を持つ単純なPojoです。

このメソッドでは、構成クラスで以前に構成したBeanインスタンスを作成する必要があります。最初に、JobDetailをQuartz JobDetail.class、ジョブの名前、グループの名前、およびスケジュールするクラス(この場合はDailyJob.class)で作成します。その後、Quartz Trigger.class、cronExpression、およびグループ名を使用してトリガーを作成する必要があります。

両方のBeanが作成されたら、今すぐジョブをスケジュールする必要があります。したがって、ジョブをスケジュールするためにQuartz Schedulerを自動配線しました。その後、ジョブが有効になり、ジョブを実行する準備が整います。

それで、ものをテストしましょう。アプリケーションを起動し、/job/create/dailyに投稿リクエストを送信します。

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

ここでは、ジョブは毎分実行する必要があると言います(すべてが機能することを確認するためだけです)。コンソールには、毎分Daily Job runs!が表示されます。

そして、あなたができるいくつかの追加の事柄があります。たとえば、スケジュールされたジョブのリストを取得します。

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

ジョブを削除するには、エンドポイントも作成できます。例えば:

@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);
}

現在実行中のジョブ、ジョブの実行頻度、ジョブの再スケジュールなどに関する情報を取得するために、さまざまなエンドポイントを自由に作成できます。重要なのは、ジョブ名とジョブグループ(この例では"MyDailyJob")が再利用可能であることだけです。これらの情報は、jobKeyを作成するために必要です。

追伸:他のジョブの他のマッピングを表示するだけです:

@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);
}

完全なアプリケーションは github にあります

3
Patrick

@Scheduledは、Beanではなくメソッドで定義されます。 Beanになる新しいクラスを作成します

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

新しいクラス:

public class Job {

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