简体   繁体   中英

How can I define multiple jobs dynamically in Spring Batch?

I have an application that uses Spring Batch to define a preset number of jobs, which are currently all defined in the XML.

We add more jobs over time which requires updating the XML, however these jobs are always based on the same parent and can easily be predetermined using a simple SQL query.

So I've been trying to switch to use some combination of XML configuration and Java-based configuration but am quickly getting confused.

Even though we have many jobs, each job definition falls into essentially one of two categories. All of the jobs inherit from one or the other parent job and are effectively identical, besides having different names. The job name is used in the process to select different data from the database.

I've come up with some code much like the following but have run into problems getting it to work.

Full disclaimer that I'm also not entirely sure I'm going about this in the right way. More on that in a second; first, the code:

@Configuration
@EnableBatchProcessing
public class DynamicJobConfigurer extends DefaultBatchConfigurer implements InitializingBean {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private JobRegistry jobRegistry;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private CustomJobDefinitionService customJobDefinitionService;

    private Flow injectedFlow1;
    private Flow injectedFlow2;

    public void setupJobs() throws DuplicateJobException {

        List<JobDefinition> jobDefinitions = customJobDefinitionService.getAllJobDefinitions();

        for (JobDefinition jobDefinition : jobDefinitions) {

            Job job = null;
            if (jobDefinition.getType() == 1) {
                job = jobBuilderFactory.get(jobDefinition.getName())
                        .start(injectedFlow1).build()
                        .build();
            } else if (jobDefinition.getType() == 2) {
                job = jobBuilderFactory.get(jobDefinition.getName())
                        .start(injectedFlow2).build()
                        .build();
            }

            if (job != null) {
                jobRegistry.register(new ReferenceJobFactory(job));
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        setupJobs();
    }

    public void setInjectedFlow1(Flow injectedFlow1) {
        this.injectedFlow1 = injectedFlow1;
    }

    public void setInjectedFlow2(Flow injectedFlow2) {
        this.injectedFlow2 = injectedFlow2;
    }
}

I have the flows that get injected defined in the XML, much like this:

<batch:flow id="injectedFlow1">

    <batch:step id="InjectedFlow1.Step1" next="InjectedFlow1.Step2">
        <batch:flow parent="InjectedFlow.Step1" />
    </batch:step>

    <batch:step id="InjectedFlow1.Step2">
        <batch:flow parent="InjectedFlow.Step2" />
    </batch:step>

</batch:flow>

So as you can see, I'm effectively kicking off the setupJobs() method (which is intended to dynamically create these job definitions) from the afterPropertiesSet() method of InitializingBean . I'm not sure that's right. It is running, but I'm not sure if there's a different entry point that's better intended for this purpose. Also I'm not sure what the point of the @Configuration annotation is to be honest.

The problem I'm currently running into is as soon as I call register() from JobRegistry , it throws the following IllegalStateException :

To use the default BatchConfigurer the context must contain no more than one DataSource, found 2.

Note: my project actually has two data sources defined. The first is the default dataSource bean which connects to the database that Spring Batch uses. The second data source is an external database, and this second one contains all the information I need to define my list of jobs. But the main one does use the default name "dataSource" so I'm not quite sure how else I can tell it to use that one.

First of all - I don't recommend using a combination of XML as well as Java Configuration. Use only one, preferably Java one as its not much of an effort to convert XML config to Java config. ( Unless you have some very good reasons to do it - that you haven't explained )

I haven't used Spring Batch alone as I have always used it with Spring Boot and I have a project where I have defined multiple jobs and it always worked well for similar code that you have shown.

For your issue, there are some answers on SO like this OR this which are basically trying to say that you need to write your own BatchConfigurer and not rely on default one.

Now coming to solution using Spring Boot

With Spring Boot, You should try segregate job definitions and job executions. You should first try to just define jobs and initialize Spring context without enabling jobs ( spring.batch.job.enabled=false )

In your Spring Boot main method, when you start app with something like - SpringApplication.run(Application.class, args); ...you will get ApplicationContext ctx

Now you can get your relevant beans from this context & launch specif jobs by getting names from property or command line etc & using JobLauncher.run(...) method.

You can refer my this answer if willing to order job executions. You can also write job schedulers using Java.

Point being, you separate your job building / bean configurations & job execution concerns.

Challenge

Keeping multiple jobs in a single project can be challenging when you try to have different settings for each job as application.properties file is environment specific and not job specific ie spring boot properties will apply to all jobs.

In my particular case, the solution was to actually eliminate the @Configuration and @EnableBatchProcessing annotations from my class above. Something about these caused it to try and use the DefaultBatchConfigurer which fails when you have more than one data source defined (even if you've identified them clearly with "dataSource" as the primary and some other name for the secondary).

The @Configuration class in particular wasn't necessary because all it really does is lets your class get auto-instantiated without having to define it as a bean in the app context. But since I was doing that anyway this one was superfluous.

One of the downsides of removing @EnableBatchProcessing was that I could no longer auto-wire the JobBuilderFactory bean. So instead I just had to do to create it:

    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.afterPropertiesSet();
    jobRepository = factory.getObject();
    jobBuilderFactory = new JobBuilderFactory(jobRepository);

Then it seems I was on the right track already by using jobRegistry.register(...) to define my jobs. So essentially once I removed those annotations above everything started working. I'm going to mark Sabir's answer as the correct one however because it helped me out.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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