如何在 Spring Batch 中动态定义多个作业?

How can I define multiple jobs dynamically in Spring Batch?

我有一个应用程序使用 Spring 批处理来定义预设数量的作业,这些作业目前都在 XML 中定义。

随着时间的推移,我们会添加更多作业,这需要更新 XML,但是这些作业始终基于相同的父作业,并且可以使用简单的 SQL 查询轻松预先确定。

所以我一直在尝试切换使用 XML 配置和基于 Java 的配置的某种组合,但很快就感到困惑。

尽管我们有很多工作,但每个工作定义本质上都属于两个类别之一。所有的作业都继承自一个或另一个父作业,并且除了名称不同之外实际上是相同的。作业名称用于select来自数据库的不同数据。

我想出了一些类似于下面的代码,但 运行 遇到了让它工作的问题。

完全免责声明 我也不完全确定我会以正确的方式解决这个问题。稍后会详细介绍;一、代码:

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

我在 XML 中定义了注入的流,就像这样:

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

正如您所见,我实际上是从 InitializingBeanafterPropertiesSet() 方法开始 setupJobs() 方法(旨在动态创建这些作业定义)。我不确定那是对的。它是 运行ning,但我不确定是否有更适合此目的的不同入口点。老实说,我也不确定 @Configuration 注释的意义所在。

我目前 运行 遇到的问题是,一旦我从 JobRegistry 调用 register(),它就会抛出以下 IllegalStateException:

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

注意:我的项目实际上定义了两个数据源。第一个是连接到 Spring Batch 使用的数据库的默认数据源 bean。第二个数据源是外部数据库,第二个数据源包含我定义工作列表所需的所有信息。但主要的确实使用默认名称 "dataSource" 所以我不太确定我还能如何告诉它使用那个名称。

首先 - 我不建议使用 XML 和 Java 配置的组合。只使用一个,最好是 Java 一个,因为将 XML 配置转换为 Java 配置并不费力。 (除非你有一些很好的理由去做 - 你没有解释

我没有单独使用 Spring Batch,因为我一直将它与 Spring Boot 一起使用,而且我有一个项目,我在其中定义了多个作业,并且它对于类似的代码总是很有效你已经展示了。

对于您的问题,SO 上有一些答案,例如 this OR this,它们基本上是想说您需要编写自己的 BatchConfigurer 而不是依赖默认的。

现在使用 Spring 引导解决方案

使用 Spring 引导,您应该尝试将作业定义和作业执行分开。 您应该首先尝试只定义作业并初始化 Spring 上下文而不启用作业 (spring.batch.job.enabled=false)

在你的 Spring Boot main 方法中,当你用类似 - SpringApplication.run(Application.class, args); 的东西启动应用程序时......你会得到 ApplicationContext ctx

现在您可以从此上下文获取相关 bean 并通过从 属性 或命令行等获取名称并使用 JobLauncher.run(...) 方法启动特定作业。

如果愿意订购作业执行,可以参考。您还可以使用 Java 编写作业调度程序。

要点是,您将作业构建/bean 配置和作业执行问题分开。

挑战

当您尝试为每个作业设置不同的设置时,在单个项目中保留多个作业可能具有挑战性,因为 application.properties 文件是特定于环境而非特定于作业的,即 spring 引导属性将应用于所有工作。

在我的特殊情况下,解决方案实际上是消除 @Configuration 和上面 class 中的 @EnableBatchProcessing 注释。关于这些的一些事情导致它尝试使用 DefaultBatchConfigurer,当您定义了多个数据源时它会失败(即使您已经用 "dataSource" 清楚地识别它们作为主要名称和其他名称作为次要名称)。

@Configuration class 尤其没有必要,因为它真正做的就是让您的 class 自动实例化,而无需在应用程序中将其定义为 bean语境。但是既然我一直在这样做,这个就多余了。

删除 @EnableBatchProcessing 的缺点之一是我无法再自动连接 JobBuilderFactory bean。所以我只需要创建它:

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

那么看来我已经走上了正确的轨道,使用 jobRegistry.register(...) 来定义我的工作。所以基本上一旦我删除了上面的那些注释,一切都开始工作了。不过,我会将 Sabir 的回答标记为正确答案,因为它帮助了我。