首先,学以致用,学习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
image.png

就业启动器

这是一个作为执行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 Boot2.2.5.RELEASEJava11maven3.5.3

另外,在Spring Batch中的配置不是使用XML,而是使用Java Config来编写。

进行实际操作试试看

下面将记录使用Spring Boot运行Spring Batch所需要做的事情。

引入图书馆

我会引入低于最低要求的库。

ライブラリ内容spring-boot-starter-batchSpring BatchをSpring Bootで利用するためのstarterライブラリh2組み込みDB
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两个值。
请注意,返回值会导致不同的行为。

RepeatStatus返り値にした場合の挙動FINISHEDそれ以上処理が続かず終了する。
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)