Spring 官方批处理框架真香,Spring 全家桶永远滴神

<java.version>1.8</java.version>

org.springframework.boot

spring-boot-starter-batch

mysql

mysql-connector-java

org.springframework.boot

spring-boot-starter-jdbc

org.springframework.boot

spring-boot-maven-plugin

在编写代码之前,我们先来简单了解下 Spring Batch 的组成:

Spring 官方批处理框架真香!Spring 全家桶永远滴神

  • Spring Batch 里最基本的单元就是任务 Job,一个 Job 由若干个步骤 Step 组成。

  • 任务启动器 Job Launcher 负责运行 Job。

  • 任务存储仓库 Job Repository 存储着 Job 的执行状态,参数和日志等信息。Job 处理任务又可以分为三大类:

  • 数据读取 Item Reader

  • 数据中间处理 Item Processor

  • 数据输出 Item Writer。

任务存储仓库可以是关系型数据库 MySQL,非关系型数据库 MongoDB 或者直接存储在内存中,本篇使用的是 MySQL 作为任务存储仓库。

新建一个名称为 springbatch 的 MySQL 数据库,然后导入 org.springframework.batch.core 目录下的 schema-mysql.sql 文件:

Spring 官方批处理框架真香!Spring 全家桶永远滴神

Spring 官方批处理框架真香!Spring 全家桶永远滴神

导入后,库表如下图所示:

Spring 官方批处理框架真香!Spring 全家桶永远滴神

然后在项目的配置文件 application.yml 里添加 MySQL 相关配置:

spring:

datasource:

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://127.0.0.1:3306/springbatch

username: root

password: 123456

接着在 Spring Boot 的入口类上添加 @EnableBatchProcessing 注解,表示开启 Spring Batch 批处理功能:

@SpringBootApplication

@EnableBatchProcessing

public class SpringBatchStartApplication {

public static void main(String[] args) {

SpringApplication.run(SpringBatchStartApplication.class, args);

}

}

至此,基本框架搭建好了,下面开始配置一个简单的任务。

编写第一个任务

=======

在 cc.mrbird.batch 目录下新建 job 包,然后在该包下新建一个 FirstJobDemo 类,代码如下所示:

@Component

public class FirstJobDemo {

@Autowired

private JobBuilderFactory jobBuilderFactory;

@Autowired

private StepBuilderFactory stepBuilderFactory;

@Bean

public Job firstJob() {

return jobBuilderFactory.get(“firstJob”)

.start(step())

.build();

}

private Step step() {

return stepBuilderFactory.get(“step”)

.tasklet((contribution, chunkContext) -> {

System.out.println(“执行步骤…”);

return RepeatStatus.FINISHED;

}).build();

}

}

上面代码中,我们注入了JobBuilderFactory任务创建工厂和StepBuilderFactory步骤创建工厂,分别用于创建任务 Job 和步骤 Step。JobBuilderFactory的get方法用于创建一个指定名称的任务,start方法指定任务的开始步骤,步骤通过StepBuilderFactory构建。

步骤 Step 由若干个小任务 Tasklet 组成,所以我们通过tasklet方法创建。tasklet方法接收一个Tasklet类型参数,Tasklet是一个函数是接口,源码如下:

public interface Tasklet {

@Nullable

RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception;

}

所以我们可以使用 lambda 表达式创建一个匿名实现:

(contribution, chunkContext) -> {

System.out.println(“执行步骤…”);

return RepeatStatus.FINISHED;

}

该匿名实现必须返回一个明确的执行状态,这里返回RepeatStatus.FINISHED表示该小任务执行成功,正常结束。

此外,需要注意的是,我们配置的任务 Job 必须注册到 Spring IOC 容器中,并且任务的名称和步骤的名称组成唯一。比如上面的例子,我们的任务名称为 firstJob,步骤的名称为 step,如果存在别的任务和步骤组合也叫这个名称的话,则会执行失败。

启动项目,控制台打印日志如下:

2020-03-06 11:01:11.785 INFO 17324 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=firstJob]] launched with the following parameters: [{}]

2020-03-06 11:01:11.846 INFO 17324 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step]

执行步骤…

2020-03-06 11:01:11.886 INFO 17324 — [ main] o.s.batch.core.step.AbstractStep : Step: [step] executed in 40ms

2020-03-06 11:01:11.909 INFO 17324 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=firstJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 101ms

可以看到,任务成功执行了,数据库的库表也将记录相关运行日志。

重新启动项目,控制台并不会再次打印出任务执行日志,因为 Job 名称和 Step 名称组成唯一,执行完的不可重复的任务,不会再次执行。

多步骤任务

=====

一个复杂的任务一般包含多个步骤,下面举个多步骤任务的例子。在 job 包下新建MultiStepJobDemo类:

@Component

public class MultiStepJobDemo {

@Autowired

private JobBuilderFactory jobBuilderFactory;

@Autowired

private StepBuilderFactory stepBuilderFactory;

@Bean

public Job multiStepJob() {

return jobBuilderFactory.get(“multiStepJob”)

.start(step1())

.next(step2())

.next(step3())

.build();

}

private Step step1() {

return stepBuilderFactory.get(“step1”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤一操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step2() {

return stepBuilderFactory.get(“step2”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤二操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step3() {

return stepBuilderFactory.get(“step3”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤三操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

}

上面代码中,我们通过step1()、step2()和step3()三个方法创建了三个步骤。Job 里要使用这些步骤,只需要通过JobBuilderFactory的start方法指定第一个步骤,然后通过next方法不断地指定下一个步骤即可。

启动项目,控制台打印日志如下:

2020-03-06 13:52:52.188 INFO 18472 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=multiStepJob]] launched with the following parameters: [{}]

2020-03-06 13:52:52.222 INFO 18472 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]

执行步骤一操作。。。

2020-03-06 13:52:52.251 INFO 18472 — [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 29ms

2020-03-06 13:52:52.292 INFO 18472 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]

执行步骤二操作。。。

2020-03-06 13:52:52.323 INFO 18472 — [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 30ms

2020-03-06 13:52:52.375 INFO 18472 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]

执行步骤三操作。。。

2020-03-06 13:52:52.405 INFO 18472 — [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 29ms

2020-03-06 13:52:52.428 INFO 18472 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=multiStepJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 231ms

三个步骤依次执行成功。

多个步骤在执行过程中也可以通过上一个步骤的执行状态来决定是否执行下一个步骤,修改上面的代码:

@Component

public class MultiStepJobDemo {

@Autowired

private JobBuilderFactory jobBuilderFactory;

@Autowired

private StepBuilderFactory stepBuilderFactory;

@Bean

public Job multiStepJob() {

return jobBuilderFactory.get(“multiStepJob2”)

.start(step1())

.on(ExitStatus.COMPLETED.getExitCode()).to(step2())

.from(step2())

.on(ExitStatus.COMPLETED.getExitCode()).to(step3())

.from(step3()).end()

.build();

}

private Step step1() {

return stepBuilderFactory.get(“step1”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤一操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step2() {

return stepBuilderFactory.get(“step2”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤二操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step3() {

return stepBuilderFactory.get(“step3”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤三操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

}

multiStepJob()方法的含义是:multiStepJob2 任务先执行 step1,当 step1 状态为完成时,接着执行 step2,当 step2 的状态为完成时,接着执行 step3。ExitStatus.COMPLETED常量表示任务顺利执行完毕,正常退出,该类还包含以下几种退出状态:

public class ExitStatus implements Serializable, Comparable {

/**

  • Convenient constant value representing unknown state - assumed not

  • continuable.

*/

public static final ExitStatus UNKNOWN = new ExitStatus(“UNKNOWN”);

/**

  • Convenient constant value representing continuable state where processing

  • is still taking place, so no further action is required. Used for

  • asynchronous execution scenarios where the processing is happening in

  • another thread or process and the caller is not required to wait for the

  • result.

*/

public static final ExitStatus EXECUTING = new ExitStatus(“EXECUTING”);

/**

  • Convenient constant value representing finished processing.

*/

public static final ExitStatus COMPLETED = new ExitStatus(“COMPLETED”);

/**

  • Convenient constant value representing job that did no processing (e.g.

  • because it was already complete).

*/

public static final ExitStatus NOOP = new ExitStatus(“NOOP”);

/**

  • Convenient constant value representing finished processing with an error.

*/

public static final ExitStatus FAILED = new ExitStatus(“FAILED”);

/**

  • Convenient constant value representing finished processing with

  • interrupted status.

*/

public static final ExitStatus STOPPED = new ExitStatus(“STOPPED”);

}

启动项目,控制台日志打印如下:

2020-03-06 14:21:49.384 INFO 18745 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=multiStepJob2]] launched with the following parameters: [{}]

2020-03-06 14:21:49.427 INFO 18745 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]

执行步骤一操作。。。

2020-03-06 14:21:49.456 INFO 18745 — [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 29ms

2020-03-06 14:21:49.501 INFO 18745 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]

执行步骤二操作。。。

2020-03-06 14:21:49.527 INFO 18745 — [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 26ms

2020-03-06 14:21:49.576 INFO 18745 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]

执行步骤三操作。。。

2020-03-06 14:21:49.604 INFO 18745 — [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 28ms

2020-03-06 14:21:49.629 INFO 18745 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=multiStepJob2]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 238ms

Flow 的用法

========

Flow 的作用就是可以将多个步骤 Step 组合在一起然后再组装到任务 Job 中。举个 Flow 的例子,在 job 包下新建FlowJobDemo类:

@Component

public class FlowJobDemo {

@Autowired

private JobBuilderFactory jobBuilderFactory;

@Autowired

private StepBuilderFactory stepBuilderFactory;

@Bean

public Job flowJob() {

return jobBuilderFactory.get(“flowJob”)

.start(flow())

.next(step3())

.end()

.build();

}

private Step step1() {

return stepBuilderFactory.get(“step1”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤一操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step2() {

return stepBuilderFactory.get(“step2”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤二操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step3() {

return stepBuilderFactory.get(“step3”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤三操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

// 创建一个flow对象,包含若干个step

private Flow flow() {

return new FlowBuilder(“flow”)

.start(step1())

.next(step2())

.build();

}

}

上面代码中,我们通过FlowBuilder将 step1 和 step2 组合在一起,创建了一个名为 flow 的 Flow,然后再将其赋给任务 Job。使用 Flow 和 Step 构建 Job 的区别是,Job 流程中包含 Flow 类型的时候需要在build()方法前调用end()方法。

启动程序,控制台日志打印如下:

2020-03-06 14:36:42.621 INFO 18865 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=flowJob]] launched with the following parameters: [{}]

2020-03-06 14:36:42.667 INFO 18865 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]

执行步骤一操作。。。

2020-03-06 14:36:42.697 INFO 18865 — [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 30ms

2020-03-06 14:36:42.744 INFO 18865 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]

执行步骤二操作。。。

2020-03-06 14:36:42.771 INFO 18865 — [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 27ms

2020-03-06 14:36:42.824 INFO 18865 — [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]

执行步骤三操作。。。

2020-03-06 14:36:42.850 INFO 18865 — [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 25ms

2020-03-06 14:36:42.874 INFO 18865 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=flowJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 245ms

并行执行

====

任务中的步骤除了可以串行执行(一个接着一个执行)外,还可以并行执行,并行执行在特定的业务需求下可以提供任务执行效率。

将任务并行化只需两个简单步骤:

  1. 将步骤 Step 转换为 Flow;

  2. 任务 Job 中指定并行 Flow。

举个例子,在 job 包下新建SplitJobDemo类:

@Component

public class SplitJobDemo {

@Autowired

private JobBuilderFactory jobBuilderFactory;

@Autowired

private StepBuilderFactory stepBuilderFactory;

@Bean

public Job splitJob() {

return jobBuilderFactory.get(“splitJob”)

.start(flow1())

.split(new SimpleAsyncTaskExecutor()).add(flow2())

.end()

.build();

}

private Step step1() {

return stepBuilderFactory.get(“step1”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤一操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step2() {

return stepBuilderFactory.get(“step2”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤二操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Step step3() {

return stepBuilderFactory.get(“step3”)

.tasklet((stepContribution, chunkContext) -> {

System.out.println(“执行步骤三操作。。。”);

return RepeatStatus.FINISHED;

}).build();

}

private Flow flow1() {

return new FlowBuilder(“flow1”)

.start(step1())

.next(step2())

.build();

}

private Flow flow2() {

return new FlowBuilder(“flow2”)

.start(step3())

.build();

}

}

上面例子中,我们创建了两个 Flow:flow1(包含 step1 和 step2)和 flow2(包含 step3)。然后通过JobBuilderFactory的split方法,指定一个异步执行器,将 flow1 和 flow2 异步执行(也就是并行)。

启动项目,控制台日志打印如下:

2020-03-06 15:25:43.602 INFO 19449 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=splitJob]] launched with the following parameters: [{}]

2020-03-06 15:25:43.643 INFO 19449 — [cTaskExecutor-1] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]

2020-03-06 15:25:43.650 INFO 19449 — [cTaskExecutor-2] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]

执行步骤三操作。。。

执行步骤一操作。。。

2020-03-06 15:25:43.673 INFO 19449 — [cTaskExecutor-2] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 23ms

2020-03-06 15:25:43.674 INFO 19449 — [cTaskExecutor-1] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 31ms

2020-03-06 15:25:43.714 INFO 19449 — [cTaskExecutor-2] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]

执行步骤二操作。。。

2020-03-06 15:25:43.738 INFO 19449 — [cTaskExecutor-2] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 24ms

2020-03-06 15:25:43.758 INFO 19449 — [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=splitJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 146ms

可以看到 step3 并没有在 step2 后才执行,说明步骤已经是并行化的(开启并行化后,并行的步骤执行顺序并不能 100%确定,因为线程调度具有不确定性)。

任务决策器

=====

决策器的作用就是可以指定程序在不同的情况下运行不同的任务流程,比如今天是周末,则让任务执行 step1 和 step2,如果是工作日,则之心 step1 和 step3。

使用决策器前,我们需要自定义一个决策器的实现。在 cc.mrbird.batch 包下新建 decider 包,然后创建MyDecider类,实现JobExecutionDecider接口:

@Component

public class MyDecider implements JobExecutionDecider {

@Override

public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {

LocalDate now = LocalDate.now();

DayOfWeek dayOfWeek = now.getDayOfWeek();

if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {

return new FlowExecutionStatus(“weekend”);

} else {

return new FlowExecutionStatus(“workingDay”);

}

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
t

public class MyDecider implements JobExecutionDecider {

@Override

public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {

LocalDate now = LocalDate.now();

DayOfWeek dayOfWeek = now.getDayOfWeek();

if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {

return new FlowExecutionStatus(“weekend”);

} else {

return new FlowExecutionStatus(“workingDay”);

}

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-QASYBLlm-1715064434841)]

[外链图片转存中…(img-g2cmN3oM-1715064434842)]

[外链图片转存中…(img-L6lIpSJP-1715064434842)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值