spring-batch day2 实战篇上

本文是Spring Batch实战的第二部分,主要介绍了如何搭建基本框架,编写入门案例,实现Job与Step的流程控制,包括Flow、Decider、异步Step、父子Job及Listener的使用。通过案例详细解释了Spring Batch如何处理批处理任务的执行顺序和逻辑控制。
摘要由CSDN通过智能技术生成

spring-batch day2 实战篇上

  本篇不探讨底层原理和一些代码含义,只介绍一些使用场景。案例使用Maven作为依赖管理工具,数据库使用MySQL5.7,使用idea作为IDE工具(idea收费,但是学生可以使用学校邮箱申请一个免费使用的账号)

1.搭好基本框架

  • 创建一个Maven项目

      这个测试案例基于JDK1.8,使用SpringBoot版本为2.0.1.RELEASE
  • 导入依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath/>
</parent>
<properties>
    <java.version>1.8</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.batch</groupId>
        <artifactId>spring-batch-test</artifactId>
            <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
  • 配置application.properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///springbatch?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=xxxx
#springbatch会将任务执行记录持久到数据库,参考的sql语句来源于此
spring.datasource.schema=classpath:/org/springframework/batch/core/schema-mysql.sql
spring.batch.initialize-schema=always
#spring.batch.job.names=parentJob

创建程序启动类SpringBatchDemoApplication:

@SpringBootApplication
public class SpringBatchDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBatchDemoApplication.class,args);
    }
}

这样,一切就准备就绪了

2.先编写一个入门的小案例

  既然SpringBatch是个批处理任务处理器,那就先让它跑起来,完成一个基本的打印任务,看看有什么结果

  • 上篇曾提到“Job是一个封装整个批处理过程的一个概念”,“事实上,每一个Job本质上都是由一个或多个步骤组成”,也就是每个批处理对应一个Job,而Job实际由Step完成。这样就有了第一份案例代码:
@Configuration
@EnableBatchProcessing
public class IntroDemoConfig {

    //注入创建任务对象的对象
    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    //任务的执行由Step决定
    //注入创建Step对象的对象
    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    //注入任务对象
    @Bean
    public Job helloWorldJob(){
        //get方法的参数是Job的名字,Job的名字不能相互冲突,由自己设置
        return jobBuilderFactory.get("helloWorldJob")
                .start(step1())
                .build();
    }

    @Bean
    public Step step1() {
        //get方法的参数是Step的名字,Step的名字不能相互冲突,由自己设置
        //tasklet是任务的具体执行逻辑,其中chunk操作是spring batch提供的“标准”动作,当然也可以自定义实现。
        return stepBuilderFactory.get("step1").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                System.out.println("执行任务");
                //表示正常处理完成
                return RepeatStatus.FINISHED;
            }
        }).build();
    }

}

启动项目,运行结果如下:

  • spring-batch自动为我们创建了如下表,并将已经执行过的任务信息持久化入数据库中。
    在这里插入图片描述

  • 以上的案例代码的任务是在控制台中打印输出一句话:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 数据库中也会由对应的记录,不过这里就不展示了

因为数据库中已经保存了“helloWordJob”的执行记录,所以在此启动项目并不会执行。而是打印如下信息:
在这里插入图片描述


@EnableBatchProcessing必须加上,不然一些类就无法加载,比如JobBuilderFactory

以上就演示了最基本的springbatch执行过程

3.演示一个Job对应多个Step:

我们知道step是任务执行的基本单位,那么一个复杂的Job必然需要多个Step完成。我们注释掉上一个案例的@Configuration和@EnableBatchProcessing,防止对我们的实验产生干扰。新代码如下:

@Configuration
@EnableBatchProcessing
public class JobDemo {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    //任务的执行由Step决定
    //注入创建Step对象的对象
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Bean
    public Job jobDemoJob(){
        return jobBuilderFactory.get("jobDemoJob")
                .start(step1())
//                .next(step2())
//                .next(step3())
                .on("COMPLETED").to(step2())
                .from(step2()).on("COMPLETED").to(step3())
                .from(step3()).end()
                .build();
    }
    @Bean
    public Step step3() {
        return stepBuilderFactory.get("step3")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("step3");
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }
    //这处的代码与上面一直,之不过将方法名改为step1() step2()stepBuilderFactory.get()传参改为对应的方法名,执行语句也改为对应的方法名,为节省篇幅,这里省去,以后遇到 【...】 均表示上述含义
    //...
}

执行结果如下:
在这里插入图片描述

  • 可以看到这次的案例和上面执行的结果一次,我们需要比较的是Job是如何管控3个Step的执行顺序的,可以看到执行顺序可以有两种方式决定:
  1. 按照next顺序顺序执行,没有end()方法,没有next()就结束
  2. 根据Job提供的on()-> to() -> from()顺序执行,直到遇到end()结束

4.Flow式操作Step

Step 是一个独立的、顺序的处理步骤,包含完整的输入、处理以及输出。但是在企业应用中,我们面对的更多情况是多个步骤按照一定的顺序进行处理。因此如何维护步骤之间的执行顺序是我们需要考虑的。Spring Batch 提供了 Step Flow :

@Configuration
@EnableBatchProcessing
public class FlowDemo {
    //注入创建任务对象的对象
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    //任务的执行由Step决定
    //注入创建Step对象的对象
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    //创建flow,指明哪些Flow对象包含哪些Step
    @Bean
    public Flow flowDemoFlow(){
        return new FlowBuilder<Flow>("flowDemoFlow")
                .start(flowDemoStep1())
                .next(flowDemoStep2())
                .build();
    }
    //创建Job对象
    @Bean
    public Job flowDemoJob(){
        return jobBuilderFactory.get("flowDemoJob")
                .start(flowDemoFlow())
                .next(flowDemoStep3())
                .end()
                .build();
    }
    @Bean
    public Step flowDemoStep1(){
        return stepBuilderFactory.get("stepBuilderFactory1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("stepBuilderFactory1");
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }
    //...
}

程序结果与上面相似:
在这里插入图片描述

不过我们更关注的是这样和上面有什么区别:
JobFlow - 一个job 可以有多个step ,每个step 有自己的逻辑,Flow可以对多个step进行排序,判断。举张图演示:
在这里插入图片描述

简单来说可以使用Flow来控制一个或多个Step的运行逻辑,相对于上一种方法逻辑性更强。

5.使用Decider控制Step流程

  Step的执行顺序,执行与否往往需要某种逻辑来控制,这时候,具有了我们的Decider。

  在此之前,我们需要知道两个概念1.BatchStatus:批处理状态和2.ExitStatus:退出状态

  1. BatchStatus:批处理状态
    批处理状态是由批处理框架使用,用来记录Job、Step的执行情况。SpringBatch的重启即是利用了BatchStauts来实现。对应BATCH_JOB_INSTANCE和BATCH_STEP_EXECUTION表中的STATUS字段值。

    JobExecution.getStatus()获取作业Job批处理状态;

    StepExecution.getStatus()获取作业Step的批处理状态;
  2. ExitStatus:退出状态
    退出状态表示Step执行后或者Job执行后的状态,该值要以被修改,通常用于条件Flow中。
    可以通过拦截器StepExecutionListener的afterStep方法来修改退出状态;
    对应BATCH_JOB_INSTANCE和BATCH_STEP_EXECUTION表中的EXIT_CODE字段值;

    状态图:
    在这里插入图片描述

案例代码:
先定义Decider,需要实现JobExecutionDecider接口:

public class MyDeciderExecutionDecider implements JobExecutionDecider {

    private int count ;
    @Override
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        count++;
        if (count%2==0){
            return new FlowExecutionStatus("even");
        }
        return new FlowExecutionStatus("odd");
    }
}

再根据Decider决定Step执行顺序:

@Configuration
@EnableBatchProcessing
public class DeciderDemo {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public JobExecutionDecider jobExecutionDecider(){
        return new MyDeciderExecutionDecider();
    }
    //创建任务

    @Bean
    public Job deciderDemoJob(){
        return jobBuilderFactory.get("deciderDemoJob")
                .start(deciderDemoStep1())
                .next(jobExecutionDecider())
                .from(jobExecutionDecider()).on("even").to(deciderDemoStep2())
                .from(jobExecutionDecider()).on("odd").to(deciderDemoStep3())
                .from(deciderDemoStep3()).on("*").to(jobExecutionDecider())
                .end().build();
    }
    //创建决策器
    //创建Step
    @Bean
    public Step deciderDemoStep1(){
        return stepBuilderFactory.get("deciderDemoStep1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("deciderDemoStep1");
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }
    //... print(even) print(odd)
}

由decider决定执行顺序,该变decider得属性count = 1 ,发现结果不再打印“odd” ,每次运行到on()方法,就会执行decider的decide()一次,返回的结果作为on()的判断依据,只有符合on得条件才会转移到下一个状态,不然会一直执行当前step。上面的代码运行逻辑是遇到偶数则打印step2,遇到奇数则打印step3,并返回继续执行decider,直到遇到偶数退出。

6.异步执行的Step

spring-batch为我们提供了异步执行Step的Simple解决方案

@Configuration
@EnableBatchProcessing
public class SplitDemo {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Bean
    public Flow splitDemoFlow1(){
        return new FlowBuilder<Flow>("splitDemoFlow1")
                .start(splitDemoStep1())
                .build();
    }
    @Bean
    public Flow splitDemoFlow2(){
        return new FlowBuilder<Flow>("splitDemoFlow2")
                .start(splitDemoStep2())
                .next(splitDemoStep3())
                .build();
    }
    //创建任务
    @Bean
    public Job splitDemoJob(){
        return jobBuilderFactory.get("splitDemoJob2")
                .start(splitDemoFlow1())
                .split(new SimpleAsyncTaskExecutor())
                .add(splitDemoFlow2()).end().build();
    }
    @Bean
    public Step splitDemoStep1(){
        return stepBuilderFactory.get("splitDemoStep1").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                System.out.println("splitDemoStep1");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }
    //...
}

执行可以看到step运行顺序不固定

7.父Job和子Job:

spring-batch可以将两个Job合并到同一个父Job下执行,需要将Job转为Step类型,演示如下

ChildJob1.java

@Configuration
public class ChildJob1 {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Bean
    public Step childJobStep1(){
        return stepBuilderFactory.get("childJobStep1").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                System.out.println("childJobStep1");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }
    @Bean
    public Job childJobOne(){
        return jobBuilderFactory.get("childJobOne").start(childJobStep1()).build();
    }
}

ChildJob2.java

@Configuration
public class ChildJob2 {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Bean
    public Step child2JobStep2(){
        return stepBuilderFactory.get("child2JobStep2").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                System.out.println("child2JobStep2");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }
    @Bean
    public Step child2JobStep1(){
        return stepBuilderFactory.get("child2JobStep1").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                System.out.println("child2JobStep1");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }
    @Bean
    public Job child2JobTwo(){
        return jobBuilderFactory.get("child2JobOne").start(child2JobStep2()).next(child2JobStep1()).build();
    }
}

NestedJobDemo.java

@Configuration
public class NestedDemo {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Autowired
    private Job childJobOne;
    @Autowired
    private Job child2JobTwo;
    @Autowired
    private JobLauncher jobLauncher;
    @Bean
    public Job parentJob(JobRepository jobRepository, PlatformTransactionManager transactionManager){
        return jobBuilderFactory.get("parentJob")
                //把子Job作为父Job的step
                .start(childJob1(jobRepository,transactionManager))
                .next(childJob2(jobRepository,transactionManager))
                .build();
    }
    //返回Job类型的Step
    private Step childJob2(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new JobStepBuilder(new StepBuilder("childJob2"))
                .job(child2JobTwo)
                .launcher(jobLauncher)//使用启动父Job的启动对象
                .repository(jobRepository)
                .transactionManager(transactionManager)
                .build();
    }
    private Step childJob1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new JobStepBuilder(new StepBuilder("childJob2")).job(childJobOne).launcher(jobLauncher)//使用启动父Job的启动对象
                .repository(jobRepository)
                .transactionManager(transactionManager)
                .build();
    }
}

8.Listener:

JobListener

  jobListener需要实现JobExecutionListener接口,实现beforeJob()和afterJob()方法,jobListener在Job启动和结束时调用。

chunkListener

  定义一个chunkListener类,定义两个方法beforeChunk()和afterChunk()方法,同时分别加上@BeforeChunk和@AfterChunk方法。chunk在Step加载了指定条数据后自动调用。

用法

ListnerDemo.java

@Configuration
public class ListenerDemo {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Bean
    public Job listenerJob(){
        return jobBuilderFactory.get("listenerJob1")
                .start(step1())
                .listener(new MyJobListener())
                .build();
    }
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                //每读完两个数据就进行数据的输出处理
                .<String,String>chunk(2)  //read process write
                .faultTolerant()
                .listener(new MyChunkListener())
                .reader(read())
                .writer(write())
                .build();
    }
    @Bean
    public ItemWriter<String> write() {

        return new ItemWriter<String>() {
            @Override
            public void write(List<? extends String> list) throws Exception {
                for (String s : list) {
                    System.out.println(s);
                }
            }
        };
    }
    @Bean
    public ItemReader<String> read() {
        return new ListItemReader<>(Arrays.asList("java","shiro","springboot"));
    }
}

MyJobListener和MyChunkListener由上面给出的方法创建

9.带参数的Job:

step级别的参数:通过StepListener接收项目启动的参数

案例:

@Configuration
@EnableBatchProcessing
public class ParametersDemo implements StepExecutionListener {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    private Map<String,JobParameter> parameters;
    @Bean
    public Job parametersJob(){
        return jobBuilderFactory.get("parametersJob4")
                .start(parameterStep())
                .build();
    }
    //Job执行的是Step ,Job使用的数据肯定是在Step中被使用
    //我们只需要给Step传递数据
    //使用Step级别的监听的方式传递数据
    @Bean
    public Step parameterStep() {
        return stepBuilderFactory.get("parameterStep")
                .listener(this)
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
                        //输出接收到的参数的值
                        System.out.println(parameters.get("info"));
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }
    @Override
    public void beforeStep(StepExecution stepExecution) {
        parameters = stepExecution.getJobParameters().getParameters();
    }
    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值