简体   繁体   English

如何按特定顺序运行Spring Batch Jobs(Spring Boot)?

[英]How to run Spring Batch Jobs in certain order (Spring Boot)?

I'm developing with Spring Batch using Spring Boot. 我正在使用Spring Boot开发Spring Batch。

I'm with the minimal configuration provided by Spring Boot and defined some Jobs (no XML configuration at all). 我使用Spring Boot提供的最小配置并定义了一些Jobs(根本没有XML配置)。 But when I run the application, 但是当我运行应用程序时,

SpringApplication.run(App.class, args);

the jobs are sequentially executed in some arbitrary order. 作业按任意顺序依次执行。

I'm defining the jobs this way in @Configuration annotated classes, Spring do the rest: 我在@Configuration注释类中以这种方式定义作业,Spring完成其余的工作:

@Bean
public Job requestTickets() {
    return jobBuilderFactory.get(Config.JOB_REQUEST_TICKETS)
            .start(stepRequestTickets())
            .build();
}

How can I instruct the framework to run the jobs in a certain order? 如何指示框架按特定顺序运行作业?

EDIT: Could this warning give a hint? 编辑:这个警告可以提示吗? (Maybe has nothing to be) (也许没什么可比的)

2016-12-29 17:45:33.320  WARN 3528 --- [main] o.s.b.c.c.a.DefaultBatchConfigurer: No datasource was provided...using a Map based JobRepository

1.You first disable automatic job start by specifying spring.batch.job.enabled=false in application.properties 1.首先通过在application.properties中指定spring.batch.job.enabled=false来禁用自动作业启动

2.In your main class, do - ApplicationContext ctx = SpringApplication.run(SpringBatchMain.class, args); 2.在你的主类中,执行 - ApplicationContext ctx = SpringApplication.run(SpringBatchMain.class, args); assuming your main class is named - SpringBatchMain.java. 假设你的主类被命名为 - SpringBatchMain.java。

This will initialize context without starting any jobs. 这将初始化上下文而不启动任何作业。

3.Once context is initialized, either you can do - JobLauncher jobLauncher = (JobLauncher) ctx.getBean("jobLauncher"); 3.Once上下文初始化,你可以做 - JobLauncher jobLauncher = (JobLauncher) ctx.getBean("jobLauncher"); or do Autowired for this JobLauncher bean in main class and launch specific jobs sequentially in specific sequential order by invoking , jobLauncher.run(job, jobParameters) . 或做Autowired在主类此JobLauncher bean并通过调用,启动特定的工作顺序的特定顺序jobLauncher.run(job, jobParameters)

You can get specific job instances from context initialized at step # 2. 您可以从步骤#2初始化的上下文中获取特定的job实例。

You can always use any ordered collection to put your jobs there and launch jobs by iterating over that collection. 您始终可以使用任何有序集合将作业放在那里,并通过迭代该集合来启动作业。

4.This above technique works as long as your JobLauncher is configured to be synchronous ie main thread waits for jobLauncher.run() call to complete and that is default behavior of jobLauncher. 4.只要您的JobLauncher配置为同步,即上述技术有效,即主线程等待jobLauncher.run()调用完成,这是jobLauncher的默认行为。

If you have defined your jobLauncher to use AsyncTaskExecutor then jobs will be started in parallel and sequential ordering will not be maintained. 如果已将jobLauncher定义为使用AsyncTaskExecutor,则将并行启动作业,并且不会保持顺序排序。

Hope it helps !! 希望能帮助到你 !!

EDIT: 编辑:

I was experimenting with @Order annotation as pointed by Stephane Nicoll and it seems to help only in creating an Ordered collection of jobs and that you can iterate and launch jobs in that order. 我正在尝试使用Stephane Nicoll指出的@Order注释,它似乎只能帮助创建一个Ordered的作业集合,并且您可以按顺序迭代并启动作业。

This below component gives me jobs in Order specified , 这个下面的组件给我指定的顺序,

@Component
public class MyJobs {
    @Autowired
    private List<Job> jobs;

    public List<Job> getJobs() {
        return jobs;
    }
}

and I can do , MyJobs myJobs = (MyJobs) ctx.getBean("myJobs"); 我能做到, MyJobs myJobs = (MyJobs) ctx.getBean("myJobs"); in main class provided bean is defined, 在主类中定义了bean,

@Bean
    public MyJobs myJobs() {
        return new MyJobs();
    }

I can iterate over myJobs and launch jobs in that order as specified by @Order annotation. 我可以迭代myJobs并按照@Order注释指定的顺序启动作业。

Order them. 订购它们。

@Bean
@Order(42)
public Job requestTickets() {
    return jobBuilderFactory.get(Config.JOB_REQUEST_TICKETS)
            .start(stepRequestTickets())
            .build();
}

See the javadoc of @Order for more details. 有关更多详细信息,请参阅@Orderjavadoc

Here is an illustration of the solution. 以下是解决方案的说明。

This is so weird, it looks like we're hacking the process. 这太奇怪了,看起来我们正在破解这个过程。

spring.batch.job.enabled=false spring.batch.job.enabled = FALSE

@SpringBootApplication
@EnableBatchProcessing
public class MyApplication {

    public static void main(String[] args)
            throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {

        ConfigurableApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
        JobLauncher jobLauncher = (JobLauncher) ctx.getBean("jobLauncher");
        Job job1= (Job) ctx.getBean("job1");
        Job job2= (Job) ctx.getBean("job2");
        jobLauncher.run(job1,new JobParameters());
        jobLauncher.run(job2,new JobParameters());
    }

}

I don't have enough rep to comment. 我没有足够的代表发表评论。 But have you tried just to manually launch your jobs in the order you want? 但您是否尝试过以您想要的顺序手动启动作业?

You need to set spring.batch.job.enabled=false in your application.properties, so that your jobs are not run automatically. 您需要在application.properties中设置spring.batch.job.enabled = false ,以便您的作业不会自动运行。

Then just use a launcher to launch your jobs in the order you want. 然后只需使用启动器按您想要的顺序启动作业。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { TestConfiguration.class, TestDataSourceConfiguration.class, TestBatchConfig.class })
public class JobOrderTest {

    @Autowired
    JobLauncher jobLauncher;

    @Mock
    Job firstJob;

    @Mock
    Job secondJob;

    @Mock
    Job thirdJob;

    @Mock
    JobParametersValidator jobParametersValidator;

    @Test
    public void jobInOrderTest() throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {

        when(firstJob.getName()).thenReturn(UUID.randomUUID().toString());
        when(secondJob.getName()).thenReturn(UUID.randomUUID().toString());
        when(thirdJob.getName()).thenReturn(UUID.randomUUID().toString());
        when(firstJob.getJobParametersValidator()).thenReturn(jobParametersValidator);
        when(secondJob.getJobParametersValidator()).thenReturn(jobParametersValidator);
        when(thirdJob.getJobParametersValidator()).thenReturn(jobParametersValidator);

        jobLauncher.run(firstJob, new JobParameters());
        jobLauncher.run(secondJob, new JobParameters());
        jobLauncher.run(thirdJob, new JobParameters());
    }

}

Here is the output 这是输出

2016-12-30 09:48:36.457  INFO 144860 --- [cTaskExecutor-1] o.s.b.c.l.support.SimpleJobLauncher      : Job: [firstJob] launched with the following parameters: ...
2016-12-30 09:48:36.457  INFO 144860 --- [cTaskExecutor-1] o.s.b.c.l.support.SimpleJobLauncher      : Job: [firstJob] completed with the following parameters: ...
2016-12-30 09:48:36.478  INFO 144860 --- [cTaskExecutor-2] o.s.b.c.l.support.SimpleJobLauncher      : Job: [secondJob] launched with the following parameters: ...
2016-12-30 09:48:36.478  INFO 144860 --- [cTaskExecutor-2] o.s.b.c.l.support.SimpleJobLauncher      : Job: [secondJob] completed with the following parameters: ...
2016-12-30 09:48:36.508  INFO 144860 --- [cTaskExecutor-3] o.s.b.c.l.support.SimpleJobLauncher      : Job: [thirdJob] launched with the following parameters: ...
2016-12-30 09:48:36.508  INFO 144860 --- [cTaskExecutor-3] o.s.b.c.l.support.SimpleJobLauncher      : Job: [thirdJob] completed with the following parameters: ...

if your one job is dependent on the second and so on, then do something like this. 如果你的一份工作依赖于第二份工作,那么就做这样的事情吧。

@Configuration
@EnableBatchProcessing
@Import(DataSourceConfiguration.class)
public class AppConfig {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public Job job(@Qualifier("step1") Step step1, @Qualifier("step2") Step step2) {
        return jobs.get("myJob").start(step1).next(step2).build();
    }

    @Bean
    protected Step step1(ItemReader<Person> reader, ItemProcessor<Person, Person> processor, ItemWriter<Person> writer) {
        return steps.get("step1")
            .<Person, Person> chunk(10)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }

    @Bean
    protected Step step2(Tasklet tasklet) {
        return steps.get("step2")
            .tasklet(tasklet)
            .build();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM