Spring Batch介绍和使用

  • 什么是Spring Batch?
  • 核心名词解释
  • 如何与springboot整合
  • 依赖


什么是Spring Batch?

  • 高效的批处理应用
  • 能够支持简单的、复杂的和大数据量的批处理作业
  • 提供了大量可重用的组件,包括了日志、追踪、事务、任务作业统计、任务重启、跳过、重复、资源管理
  • 提供了高级功能和特性来支持,比如分区功能、远程功能

核心名词解释

  • Spring Batch运行的基本单位是一个Job,一个Job就做一件批处理的事情。
    一个Job包含很多Step,step就是每个job要执行的单个步骤
  • Step里面,会有Tasklet,Tasklet是一个任务单元,它是属于可以重复利用的东西。然后是Chunk,chunk就是数据块,你需要定义多大的数据量是一个chunk,Chunk里面就是不断循环的一个流程,读数据,处理数据,然后写数据。Spring Batch会不断的循环这个流程,直到批处理数据完成.
  • Spring batch在配置Step时采用的是基于Chunk的机制,即每次读取一条数据,再处理一条数据,累积到一定数量后再一次性交给writer进行写入操作。这样可以最大化的优化写入效率,整个事务也是基于Chunk来进行

比如我们定义chunk size是50,那就意味着,spring batch处理了50条数据后,再统一向数据库写入。
这里有个很重要的点,chunk前面需要定义数据输入类型和输出类型,由于我们输入是Message,输出也是Message,所以两个都直接写Message了。
如果不定义这个类型,会报错。.<Message, Message>chunk(CHUNK_SIZE)

  • Reader顾名思义就是从数据源读取数据。
    Spring Batch给我们提供了很多好用实用的reader,基本能满足我们所有需求。比如FlatFileItemReader,JdbcCursorItemReader,JpaPagingItemReader等。也可以自己实现Reader

tasklet spring batch跟项目不在一个项目 springboot spring batch_spring


-jobRepository:定义Job时,需要指定一个JobRepository,用来存储Job在运行过程中的状态信息,对于存储状态信息的原因为:如果Job运行失败了,Spring支持从失败的地方重新运行,而不是从头开始;

  • JobLauncher:很好理解launchuer是用来执行Job的,如果不设置,系统也会默认给Job配置默认的Launcher;

如何与springboot整合

依赖

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	注意以下依赖
	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
			<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
```	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

 - 配置文件需要配置

>  spring.datasource.url=jdbc:mysql://localhost:3306/springbatch?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=qitao1996
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
>  spring.batch.job.names = job1,job2 #启动时要执行的Job,默认执行全部Job
spring.batch.job.enabled=true #是否自动执行定义的Job,默认是
spring.batch.initializer.enabled=true #是否初始化Spring Batch的数据库,默认为是
spring.batch.table-prefix=batch_  #设置SpringBatch的数据库表的前缀
#JPA
spring.jpa.properties.hibernate.hbm2ddl.auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.database = MYSQL
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.show-sql = true
 - 开启batch,@EnableBatchProcessing是打开Batch。如果要实现多Job的情况,需要把EnableBatchProcessing注解的modular设置为true,让每个Job使用自己的ApplicationConext。
  • 构建Job,
  • 以上配置完后,spring会自动为我们注入以下对象
@Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;
    //主要负责从JobRepository中获取执行的信息,包括获取作业实例、获取作业执行器、获取作恶步执行器、获取正在运行的作业执行器、获取作业列表等操作;
    @Autowired
    public JobExplorer jobExplorer;

    @Autowired
    public JobRepository jobRepository;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    EntityManagerFactory entityManagerFactory;


@Bean
public Step messageMigrationStep(@Qualifier("jsonMessageReader") FlatFileItemReader<Message> jsonMessageReader,
                                 @Qualifier("messageItemWriter") JpaItemWriter<Message> messageItemWriter,
                                 @Qualifier("errorWriter") Writer errorWriter) {
    return stepBuilderFactory.get("messageMigrationStep")
            .<Message, Message>chunk(CHUNK_SIZE)
            .reader(jsonMessageReader).faultTolerant().skip(JsonParseException.class).skipLimit(SKIP_LIMIT)
            .listener(new MessageItemReadListener(errorWriter))
            .writer(messageItemWriter).faultTolerant().skip(Exception.class).skipLimit(SKIP_LIMIT)
            .listener(new MessageWriteListener())
            .build();
}
#我们的Step中可以构建很多东西,比如reader,processer,writer,listener等等。

解释以上代码




@Bean
 public FlatFileItemReader jsonMessageReader() {
 FlatFileItemReader reader = new FlatFileItemReader<>();
 reader.setResource(new FileSystemResource(new File(MESSAGE_FILE)));
 reader.setLineMapper(new MessageLineMapper());
 return reader;
 }
  • LineMapper的输入就是获取一行文本,和行号,然后转换成Message。
    在本例子里面,一行文本就是一个json对象,所以我们使用JsonParser来转换成Message。
public class MessageLineMapper implements LineMapper<Message> {
    private MappingJsonFactory factory = new MappingJsonFactory();

    @Override
    public Message mapLine(String line, int lineNumber) throws Exception {   
        JsonParser parser = factory.createParser(line);
        Map<String, Object> map = (Map) parser.readValueAs(Map.class);
        Message message = new Message();
        ... // 转换逻辑
        return message;
    }
}

@Service
public class MyProcessorItem implements ItemProcessor<Article,Article> {

    Logger logger = LoggerFactory.getLogger(MyProcessorItem.class);

    @Override
    public Article process(Article article)throws Exception{
       logger.info("【{}】经过处理器",article.getTitle());
        return article;
    }
}

@Autowired
private EntityManagerFactory entityManager;

@Bean
public JpaItemWriter<Message> messageItemWriter() {
    JpaItemWriter<Message> writer = new JpaItemWriter<>();
    writer.setEntityManagerFactory(entityManager);
    return writer;
}

public class MessageItemReadListener implements ItemReadListener<Message> {
    private Writer errorWriter;

    public MessageItemReadListener(Writer errorWriter) {
        this.errorWriter = errorWriter;
    }

    @Override
    public void beforeRead() {
    }

    @Override
    public void afterRead(Message item) {
    }

    @Override
    public void onReadError(Exception ex) {
         errorWriter.write(format("%s%n", ex.getMessage()));
    }
}
  • Spring Batch提供了skip的机制,也就是说,如果出错了,可以跳过。如果你不设置skip,那么一条数据出错了,整个job都会挂掉。
    设置skip的时候一定要设置什么Exception才需要跳过,并且跳过多少条数据。如果失败的数据超过你设置的skip limit,那么job就会失败。
writer(messageItemWriter).faultTolerant().skip(Exception.class).skipLimit(SKIP_LIMIT)
  • Retry这个和Skip是一样的原理,就是失败之后可以重试,你同样需要设置重试的次数。同样可以分别给reader,writer等设置retry机制。
    如果同时设置了retry和skip,会先重试所有次数,然后再开始skip。比如retry是10次,skip是20,会先重试10次之后,再开始算第一次skip。
  • 运行Job


JobRegistry jobRegistry = context.getBean(JobRegistry.class);
        Job job = jobRegistry.getJob(jobName);
        JobLauncher jobLauncher = context.getBean(JobLauncher.class);
        JobExecution jobExecution = jobLauncher.run(job, createJobParams());
  • 数据库查看会自动生成相关表