首先,学以致用,学习Spring Boot Batch的运行方法
※注意(2023/05/13更新)
请参考官方文档以获取最新的信息,Spring Boot 3.0已经发布并且Spring Batch也升级至5.0版本。
首先
Spring Batch是一个可以在Spring上构建批处理应用程序的框架。学习Spring Batch涉及到许多角色和一些复杂的概念。当然,学习这些是非常重要的,但首先我们想要尝试运行起来!因此,本文将重点放在使用Spring Boot来运行Spring Batch的必要步骤上。
我们将尽量减少概念的解释,更加注重实践,目标是先感受一下Spring Batch是如何运作的,而不是只关注理论。
本文的目标是什么?
-
- Spring BootでSpring Batchをどのように書けばいいか分かる
-
- Spring Batchの全体像がなんとなく分かる
-
- 簡単なJob, Stepを記述できる(今回はTaskletモデルを紹介)
- 必要な最低限のライブラリ、Java Configの記述が分かる
Spring Batch的概念
這裡介紹應該首先了解的基本概念。
更多詳細內容請參閱官方參考資料。
整体的形象。
这是Spring Batch的整体架构。只要记清楚这个即可。
这里将对以下关键词进行解释说明。
-
- JobLauncher
-
- Job
-
- JobParameter
-
- Step
- JobRepository
就业启动器
这是一个作为执行Job的入口点的接口。通过调用run方法来执行Job。run方法接收要执行的Job和JobParameter。
public interface JobLauncher {
public JobExecution run(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException,
JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}
工作
Spring Batch是一个基本元素,用于表示整个批处理过程。可以将1个Job视为1个批处理。一个Job由多个Step组成,执行一个Job时,将执行构成该Job的多个Step。
工作参数
可以在作业执行时传递的参数。这些参数可以作为作业或步骤的变量使用。
只需要一个选项
步骤
Step是组成批处理的最小单位。Step可分为两种主要模型。
-
- Chunkモデル
読み込み(ItemReader)、加工(ItemProcessor)、書き込み(ItemWriter)によって構成されるステップモデル。読み込み、加工、書き込みの順でStepが構成され、必ずそれぞれの処理が実行されます(例えば読み込み、加工だけを行うようなStepにはできません)。実装者はそれぞれの処理内容を記述する必要があります。予めSpring Batch側でインタフェースが提供されているので、実装者はそれを実装してそれぞれの処理内容を書いていきます。
Taskletモデル
Chunkモデルとは違い、特に処理の流れが決まっておらず、自由に処理を記述できるステップモデル。何か単一処理を行う場合はこちらを利用します。こちらもSpring Batch側でインタフェースが提供されているので、実装者はインタフェースを実装して処理内容を書いていきます。
职位存储库
将Job或Step的执行状态和执行结果保存在一个地方。通常情况下,我们会使用RDB等存储介质进行持久化。
本文介绍的批处理应用程序的内容
本次我们将创建一个简单的批处理应用程序,当启动Spring Boot时,它将显示“Hello, World!”。请在GitHub上查看源代码。
前提是只需要一种选择
这个代码在以下版本中已经过测试验证。
另外,在Spring Batch中的配置不是使用XML,而是使用Java Config来编写。
进行实际操作试试看
下面将记录使用Spring Boot运行Spring Batch所需要做的事情。
引入图书馆
我会引入低于最低要求的库。
JobRepositoryを格納するために必要
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
...
</dependencies>
...
Tasklet的实现
首先我们需要实现一个作为Step实际处理的Tasklet。(由于本次没有写入操作,所以采用了Tasklet模型)
以下是用于显示”Hello, World!”的Tasklet。
我们要实现Tasklet接口并编写execute方法。
execute方法需要返回RepeatStatus类型的返回值。
RepeatStatus是一个枚举类型,具有RepeatStatus.FINISHED和RepeatStatus.CONTINUABLE两个值。
请注意,返回值会导致不同的行为。
nullを返した場合もFINISHEDと同様の挙動になる。CONTINUABLETaskletが継続して呼ばれる。
FINISHEDを返さない限りは処理が継続するので注意が必要。
@Component
@StepScope
public class HelloWorldTasklet implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Hello, World!");
return RepeatStatus.FINISHED;
}
}
工作或步骤的定义
在Java Config中,我们主要使用Bean定义来描述Job或Step的设置。
通常情况下,还需要定义一些Bean如JobRepository和JobLauncher,但是在Spring Batch中,有一个名为@EnableBatchProcessing的注解,当我们添加该注解时,它会自动为我们进行设置,而不需要明确地定义它们。
@EnableBatchProcessing
@Configuration
public class BatchConfig {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final HelloWorldTasklet helloWorldTasklet;
public BatchConfig(JobBuilderFactory jobBuilderFactory,
StepBuilderFactory stepBuilderFactory,
HelloWorldTasklet helloWorldTasklet) {
this.jobBuilderFactory = jobBuilderFactory;
this.stepBuilderFactory = stepBuilderFactory;
this.helloWorldTasklet = helloWorldTasklet;
}
@Bean
public Job helloWorldJob(Step helloWorldStep) {
return jobBuilderFactory.get("helloWorldJob") //Job名を指定
.flow(helloWorldStep) //実行するStepを指定
.end()
.build();
}
@Bean
public Step helloWorldStep() {
return stepBuilderFactory.get("helloWorldStep") //Step名を指定
.tasklet(helloWorldTasklet) //実行するTaskletを指定
.build();
}
}
首先,让我们对上述代码进行一些解释。
首先,是关于Job的部分。在jobBuilderFactory.get()方法的参数中指定了一个Job名称。在这里,我们指定了一个名为”helloWorldJob”的名称。接下来,使用.flow()方法指定了在此Job中要执行的Step。
接下来是Step。与Job的构成相似,首先需要在stepBuilderFactory.get()方法的参数中指定Step的名称。这里我们指定了名称为”helloWorldStep”。然后使用tasklet()方法指定要执行的Tasklet。
数据源的设置
本次我们将使用H2作为JobRepository的存储位置,因此需要编写相应的DataSource设置。
spring:
datasource:
url: "jdbc:h2:mem:test"
username: sa
password:
driver-class-name: org.h2.Driver
实现SpringBootApplication
这与一般的Spring Boot启动类没有任何区别。
@SpringBootApplication
public class SampleSpringBatchApplication {
public static void main(String[] args) {
SpringApplication.run(SampleSpringBatchApplication.class, args);
}
}
以上是設定完成!
启动
只需要一个选项,以下是中文的翻译:
只需要编译并启动,批处理将被执行。
在Spring Boot中,所有已定义的Bean作业默认会被执行,因此无需特别设置,在启动时作业会被执行。
下面是启动日志,可以看到已定义的helloWorldJob正在执行。
$ ./mvnw spring-boot:run
...(省略)...
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE)
...(省略)...
2020-03-24 23:12:36.494 INFO 76528 --- [ main] c.k.s.SampleSpringBatchApplication : Started SampleSpringBatchApplication in 2.261 seconds (JVM running for 2.83)
2020-03-24 23:12:36.496 INFO 76528 --- [ main] o.s.b.a.b.JobLauncherCommandLineRunner : Running default command line with: []
2020-03-24 23:12:36.568 INFO 76528 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=helloWorldJob]] launched with the following parameters: [{}]
2020-03-24 23:12:36.638 INFO 76528 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [helloWorldStep]
Hello, World!
2020-03-24 23:12:36.672 INFO 76528 --- [ main] o.s.batch.core.step.AbstractStep : Step: [helloWorldStep] executed in 34ms
2020-03-24 23:12:36.678 INFO 76528 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=helloWorldJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 63ms
...(省略)...
总结
我已经解释了最低限度执行Spring Boot中使用Spring Batch的步骤。最后,我将列出我们所做的必要事项。
-
- spring-boot-starter-batchの追加
-
- DBの用意、DataSourceの設定
-
- JobやStepなどの設定(Java Config)
- Step処理の記述(今回はTaskletの記述)
我这次在解释中省略了其他概念和详细执行机制的部分,打算在以后的某个时候再写一篇文章来解释。
補充
我将在下面补充一些关于上面没有提到的内容的解释。
关于@StepScope
我們在這次實現的 HelloWorldTasklet 類中加上了 @StepScope 這個註釋。
這個註釋用於定義 Bean 的生成範圍,Step Scope 是確保在同一個 Step 中只使用同一個實例的範圍。如果 Step 改變,實例會被重新生成。默認情況下,是單例範圍,所以在 Tasklet 中如果有狀態的話,存在被傳遞到其他 Step 的風險。如果沒有問題的話,那就沒問題,但如果有問題的話,我認為最好設置為 Step Scope。
Step Scope 只能在 Spring Batch 中使用。
如果要用Chunk模型构建Step,Step的定义方式是怎样的
我计划在另一篇文章中提及这个地方。
只需要提供一种选择:
当定义了多个Job时,如何限制在启动时执行的Job。
当使用Spring Boot启动Spring Batch时,默认情况下会执行所定义的所有作业,但有时可能希望限制只执行特定的作业。在这种情况下,可以通过指定spring.batch.job.names属性来限制要执行的作业。如果想指定多个作业,可以使用逗号进行分割。
spring:
batch:
job:
names: helloWorldJob
如果您希望在启动时禁用所有作业的执行,可以将spring.batch.job.enabled设为false来实现。这对于在通过@SpringBootTest进行测试时不想运行作业的情况非常有用。在示例测试中,spring.batch.job.enabled已被设置为false。
spring:
batch:
job:
enabled: false
JobLauncher没有在什么地方被调用,这一点并没有提及。
在概念解释部分,我们提到了通过调用JobLauncher的run方法来执行Job,但在示例应用中并没有特别说明JobLauncher的调用位置。那么,它是在哪里被调用的呢?
答案可以在 Spring Boot 的 JobLauncherCommandLineRunner 类中找到。该类实现了 CommandLineRunner 接口,因此在 Spring Boot 启动时会调用 run 方法。通过追踪 run 方法,我们可以发现在 execute 方法中调用了 JobLauncher 的 run 方法。
public class JobLauncherCommandLineRunner implements CommandLineRunner, Ordered, ApplicationEventPublisherAware {
@Override
public void run(String... args) throws JobExecutionException {
logger.info("Running default command line with: " + Arrays.asList(args));
launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
}
protected void launchJobFromProperties(Properties properties) throws JobExecutionException {
JobParameters jobParameters = this.converter.getJobParameters(properties);
executeLocalJobs(jobParameters);
executeRegisteredJobs(jobParameters);
}
//(略)
protected void execute(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException,
JobParametersInvalidException, JobParametersNotFoundException {
JobParameters parameters = getNextJobParameters(job, jobParameters);
JobExecution execution = this.jobLauncher.run(job, parameters); //Jobの実行を呼び出している
if (this.publisher != null) {
this.publisher.publishEvent(new JobExecutionEvent(execution));
}
}
在JobLauncher的run方法中,我们从哪里获取了传入的Job参数?回溯一下,我们发现在setJobs方法中,Job的Collection被setter注入了进来。换句话说,我们通过DI(依赖注入)来获取Bean定义的Job。
顺便提一句,如果在application.yml中指定了spring.batch.job.names,那么可以通过探索BatchAutoConfiguration类的Bean方法jobLauncherCommandLineRunner来获取Job的名称,在该方法中将其设置为jobLauncherCommandLineRunner类的字段,并最终通过jobLauncherCommandLineRunner类的executeRegisteredJobs方法从Job名称获取Job。
private void executeRegisteredJobs(JobParameters jobParameters) throws JobExecutionException {
if (this.jobRegistry != null && StringUtils.hasText(this.jobNames)) {
String[] jobsToRun = this.jobNames.split(",");
for (String jobName : jobsToRun) {
try {
Job job = this.jobRegistry.getJob(jobName); //Job名からJobを取得
if (this.jobs.contains(job)) {
continue;
}
execute(job, jobParameters);
}
catch (NoSuchJobException ex) {
logger.debug(LogMessage.format("No job found in registry for job name: %s", jobName));
}
}
}
}
自定义JobLauncher和JobRepository的方法
如果您想自定义JobLauncher和JobRepository的设置,您可以实现BatchConfigurer接口进行自定义,并将该类进行Bean定义即可。由于在此我们不提供详细说明,请参考官方参考文档。
引用
-
- Spring Batch – Reference Documentation
-
- https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-batch-applications
- Overview (Spring Batch 4.2.0.RELEASE API)