文章目录

  • 什么是SpringBatch
  • Spring Batch核心概念介绍
  • springBacth的使用场景
  • springBoot集成springbatch
  • 1. 导入依赖
  • 2、配置类
  • 3、异常:Table ‘springbatch.batch_job_instance‘ doesn‘t exist
  • 4、一些方法的说明
  • 核心Api说明:
  • Flow
  • Split实现并发执行
  • 决策器的使用(Decider)
  • Job的嵌套
  • 监视器的使用
  • Job参数


什么是SpringBatch

其实我都不喜欢写这些看起来头疼的东西!!!

相信大家也一样

根据官网的说法:

  • Spring Batch是一个轻量级的、完善的批处理应用框架,旨在支持企业系统建立健壮、高效的批处理应用。然而Spring Batch不是一个调度框架,它只关注于任务的处理,如日志监控、事务、并发问题等,但是它可以与其它调度框架一起联合使用,完成相应的调度任务,如Quartz、Tivoli、Control-M等
  • Spring Batch提供了很多非常实用的组件,包括了日志/跟踪、事务管理、作业处理统计、作业重新启动、跳过和资源管理。它还提供了更先进的技术服务和功能,支持通过优化和分区技术实现极高容量和高性能的批处理作业。Spring Batch既可以用于简单的用例(例如将文件读入数据库或运行存储过程),也可以用于复杂的、大容量的用例(例如在数据库之间移动大容量的数据、转换数据等等)。高容量批处理作业可以以高度可伸缩的方式利用框架来处理大量信息。

看了上面一堆,那么springBatch到底是什么呢?

Spring Batch核心概念介绍

先看一张图:

spring batch 会重复执行 spring batch没啥用_ide

springBatch框架一共有4个角色组成:

  • JobLauncher 是任务启动器,通过它来启动任务,可以看做是程序的入口。
  • Job 代表着一个具体的任务。
  • Step 代表着一个具体的步骤,一个 Job 可以包含多个Step(想象把大象放进冰箱这个任务需要多少个步骤你就明白了) .
  • ItemReader:数据的读取
  • ItemProcessor:数据的处理
  • ItemWriter:数据的输出
  • JobRepository 是存储数据的地方,可以看做是一个数据库的接口,在任务执行的时候需要通过它来记录任务状态等等信息。

springBacth的使用场景

官方说明:

  1. Transaction management(事务管理)
  2. Chunk based processing(基于块的处理)
  3. Declarative I/O(声明式的输入输出)
  4. Start/Stop/Restart(启动/停止/再启动)
  5. Retry/Skip(重试/跳过)

场景:

  1. 从数据库,文件或队列中读取大量记录。
  2. 以某种方式处理数据。
  3. 以修改后的形式写回数据。

springBoot集成springbatch

一个最基础的案例

因为是集成springboot所以使用【spring-boot-starter-batch】这个依赖

1. 导入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.batch/spring-batch-core -->
        <!-- 集成springboot batch 批处理 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
        <!-- Spring Boot JDBC -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--         druid数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <!-- mysql 连接 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.14</version>
            <scope>provided</scope>
        </dependency>
        <!--common-lang 常用工具包 -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--commons-lang3 工具包 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>

2、配置类

也可以说是一个执行类

package com.example.demo.configuration;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
@EnableBatchProcessing
public class BatchConfig {

    /**
     * 创建job任务的工厂
     */
    @Resource
    private JobBuilderFactory jobBuilderFactory;

    /**
     * 创建step步骤的工厂
     */
    @Resource
    private StepBuilderFactory stepBuilderFactory;

    /**
     * get("myJob") ——> 获取一个任务的名称,可能跟bean的名称不一样
     * .start(myStep()) ——> 开始执行什么步骤,它们是一个一对多的关系
     . next(myStep1()) ——> 代表下一个Step
     * @return 任务
     */
    @Bean
    public Job myJob() {
        return jobBuilderFactory.get("myJob")
            .start(myStep())
            .next(myStep1())
            .build();
    }

    /**
     * get("myStep") ——> 获取一个步骤的名称,可能跟bean的名称不一样
     * .tasklet ——> 创建一个步骤
     * @return 任务
     */
    @Bean
    public Step myStep() {
        return stepBuilderFactory.get("myStep").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws 				Exception {
                System.out.println("正在执行中");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }
    
       @Bean
    public Step myStep1() {
        return stepBuilderFactory.get("myStep1").tasklet(new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws 				Exception {
                System.out.println("正在执行中1");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }
}

到这里我们就可以启动了

然后如果我们使用的是MySQL作为Spring Batch数据库时,就会报以下异常:

3、异常:Table ‘springbatch.batch_job_instance‘ doesn‘t exist

我们只需要在数据库加上springbatch需要的表就行了,也可以在yml里直接配置,就不需要手动创建表了

spring:
  datasource:
    druid:
      url: jdbc:mysql://127.0.0.1:3306/springbatch?characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
  batch:
    jdbc:
      initialize-schema: always #初始化

4、一些方法的说明

使用二主要说明配置类的一些方法的使用与意思

@Bean
public Job myJob() {
    return jobBuilderFactory.get("myJob")
        .start(myStep())
        .on("COMPLETED")//当myStep执行成功
        //  .fail() // 让上一个step失败
        // .stopAndRestart() 停止并重启启动,一般用于测试
        .to(myStep1())// 则执行myStep1
        .from(myStep1())// 执行完myStep1后
        .end()//结束
        .build();
}

核心Api说明:

1、JobInstance:

该领域概念和job的关系与java中实例和类的关系一样,Job定义了一个工作流程,Jobinstance就是该工作流程的一个具体实现,一个Job可以有多个JobInstance,多个Job之间的区分就要靠另外一个领域概念JobParameters了

2、JobParameters:

是一组可以贯穿整个Job的运行时配置参数。不同的配置将产生不同的JobInstance,如果你是使用相同的JobParameters运行同一个Job,那么这次运行会重用上一次创建的JobInstance,另外,springbatch还非常贴心的提供了让jobParameters中的部分参数不参与JobInstance区分的任务

3、JobExecution:

该领域概论表示JobInstance的一次执行,JobInstance运行时可能会成功或者失败,每一次JobInstance运行都会产生一个JobExecution,同一个JobInstance(JobParameters相同)可以多次运行,这样该JobInstance对应多个JobExecution。

4、StepExecution:

类似于JobExecution,该领域表示Step的一次执行,step是job的一部分,因此一个StepExecution会关联到一个JobExecution。另外,该对象还会存储很多与该次Step运行相关的所有数据。因此该对象也有很多属性。并且需要持久化以支持一些springbatch的一些特性

5、ExecutionContext:

从前面的JobExecution、StepExecution的属性介绍已经提到该领域概念,说穿了,该领域概念就是一个容器,该容器由batch框架控制,框架会对该容器持久化,开发人员可以使用该容器保存一些数据,以支持在整个batchJob或者整个Step中共享这些数据

spring batch 会重复执行 spring batch没啥用_spring batch 会重复执行_02

Flow

  1. flow是多个step的集合
  2. 可以被多个Job复用
  3. 使用flowBuilder创建
@Bean
    public Job myJob() {
        return jobBuilderFactory.get("myJob")
                .start(flowDemo())
                .end()
                .build();
    }

    /**
     * 创建一个flow,指明flow中包含哪些step
     * @return
     */
    @Bean
    public Flow flowDemo(){
        return new FlowBuilder<Flow>("flowDemo")
                .start(myStep())
                .next(myStep1())
                .build();
    }

flow跟job其实只是一个搭配,其中的方法也是可以链式衔接的

Split实现并发执行

实现任务中的多个Step或多个flow并发执行

  1. 创建若干个step
  2. 创建两个flow
  3. 创建一个任务包含以上两个flow,并让这两个flow并发执行
@Bean
    public Job splitDemoJob(){
        return jobBuilderFactory.get("splitDemoJob")
                .start(myFlow1())
                .split(new SimpleAsyncTaskExecutor())
                .add(myFlow2())
                .end().build();
    }

new SimpleAsyncTaskExecutor() : 为创建一个新的线程异步执行

决策器的使用(Decider)

决策器是决定当一个step执行之后根据什么样的决策再执行下一个step

1、配置决策器:

public class MyDecider implements JobExecutionDecider {
    int count;
    @Override
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        count++;
        if (count%2 == 0){
            return new FlowExecutionStatus("1");
        }else {
            return new FlowExecutionStatus("2");
        }
    }
}

2、使用:

/**
     * 注入自己创建的Decider
     */
    @Bean
    public JobExecutionDecider myJobDecider(){
        return new MyDecider();
    }

    @Bean
    public Job jobDemoDecider(){
        return jobBuilderFactory.get("jobDemoDecider")
                .start(deciderDemoStep1())
                .next(myJobDecider())
                .from(myJobDecider()).on("1").to(deciderDemoStep2())
                .from(myJobDecider()).on("2").to(deciderDemoStep3())
                .from(deciderDemoStep3()).on("*").to(myJobDecider())
                .end().build();
    }

执行结果:deciderDemoStep1—> deciderDemoStep3—>deciderDemoStep2

这里job执行的意思是:先执行deciderDemoStep1,再执行决策器,当执行决策器的结果是“1”时执行deciderDemoStep2,当执行决策器的结果是“2”时执行deciderDemoStep3;当执行deciderDemoStep3时不管是什么结果(“*”)都执行myJobDecider

‘那么就会重新执行决策器,那么决策器这次结果将是1,会执行deciderDemoStep2’

执行完所有步骤

Job的嵌套

一个Job可以嵌套在另一个Job中,被嵌套的Job称为子Job,外部Job称为父Job,子Job不能单独执行,需要父Job来启动

栗子:

package com.example.demo.configuration;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.JobStepBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.Resource;

@Configuration
public class NestedDemo {

    /**
     * 创建job任务的工厂
     */
    @Resource
    private JobBuilderFactory jobBuilderFactory;

    /**
     * 创建step步骤的工厂
     */
    @Resource
    private StepBuilderFactory stepBuilderFactory;

    @Resource
    private Job jobDemo1;

    @Resource
    private Job jobDemo2;

    @Resource
    private JobLauncher jobLauncher;


    @Bean
    public Job parentJob(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager){
        return jobBuilderFactory.get("parentJob")
                .start(jobStepDemo1(jobRepository,platformTransactionManager))
                .next(jobStepDemo2(jobRepository,platformTransactionManager))
                .build();
    }

    /**
     * 创建一个Job类型的Step,是一个特殊的Step
     * @param jobRepository Job工作库
     * @param platformTransactionManager 平台事务管理器
     */
    @Bean
    public Step jobStepDemo1(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager) {
        return new JobStepBuilder(new StepBuilder("jobStepDemo1"))
                .job(jobDemo1)
                .launcher(jobLauncher) // 使用父job的启动器
                .repository(jobRepository)
                .transactionManager(platformTransactionManager).build();

    }

    @Bean
    public Step jobStepDemo2(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager) {
        return new JobStepBuilder(new StepBuilder("jobStepDemo2"))
                .job(jobDemo2)
                .launcher(jobLauncher)
                .repository(jobRepository)
                .transactionManager(platformTransactionManager).build();

    }
}

监视器的使用

用来监听批处理的执行情况

创建监听可以通过实现接口或使用注解

  1. JobExecutionListener(before,after)
  2. StepExecutionListener(before,after)
  3. ChunkListener(before,after,error)
  4. ItemReadListener,ItemProcessListener,ItemWriteListener(before,after,error)

1、创建Job执行监视器:

package com.example.demo.configuration;


import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;

public class MyJobListener implements JobExecutionListener {


    @Override
    public void beforeJob(JobExecution jobExecution) {
        System.out.println(jobExecution.getJobInstance().getJobName()+"....beforeJob");
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        System.out.println(jobExecution.getJobInstance().getJobName()+"....afterJob");
    }
}

2、创建块监视器:

package com.example.demo.configuration;

import org.springframework.batch.core.annotation.AfterChunk;
import org.springframework.batch.core.annotation.BeforeChunk;

public class MyChunkListener {
    @BeforeChunk
    public void chunkBefore(){
        System.out.println("chunkBefore");
    }

    @AfterChunk
    public void chunkAfter(){
        System.out.println("chunkAfter");
    }
}

3:使用

package com.example.demo.configuration;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;

@Configuration
@EnableBatchProcessing
public class JobListenerDemo {
    /**
     * 创建job任务的工厂
     */
    @Resource
    private JobBuilderFactory jobBuilderFactory;

    /**
     * 创建step步骤的工厂
     */
    @Resource
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job jobListener1(){
        return jobBuilderFactory.get("jobListener1")
                .start(step1())
                .listener(new MyJobListener())
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<String,String>chunk(2) // 读完多少条数据执行
                .faultTolerant() // 容错处理
                .listener(new MyChunkListener())
                .reader(reader())
                .writer(writer())
                .build();
    }

    @Bean
    public ItemWriter<String> writer() {
        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> reader() {
        return new ListItemReader<>(Arrays.asList("java","spring","mybatis"));
    }
}

Job参数

在Job运行中可以以key = value的形式传递参数

@Configuration
@EnableBatchProcessing
public class ParametersDemo implements StepExecutionListener {

    @Resource
    private JobBuilderFactory jobBuilderFactory;

    @Resource
    private StepBuilderFactory stepBuilderFactory;

    private Map<String,JobParameter> jobParameterMap;

    @Bean
    public Job parametersJob() {
        return jobBuilderFactory.get("parametersJob1")
                .start(parametersStep())
                .build();
    }

    /**
     * Job执行的是Step,Job使用的数据肯定是在Step中使用
     * 所以使用Step级别的监听来传递参数(StepExecutionListener)
     */
    @Bean
    public Step parametersStep() {
        return stepBuilderFactory.get("parametersStep1")
                .listener(this)
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("结果:"+jobParameterMap.get("info"));
                        return RepeatStatus.FINISHED;
                    }
                }).build();
    }


    @Override
    public void beforeStep(StepExecution stepExecution) {
        jobParameterMap = stepExecution.getJobParameters().getParameters();
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return null;
    }
}