如何在 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>
正如您所见,我实际上是从 InitializingBean
的 afterPropertiesSet()
方法开始 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 的回答标记为正确答案,因为它帮助了我。
我有一个应用程序使用 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>
正如您所见,我实际上是从 InitializingBean
的 afterPropertiesSet()
方法开始 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(...)
方法启动特定作业。
如果愿意订购作业执行,可以参考
要点是,您将作业构建/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 的回答标记为正确答案,因为它帮助了我。