Spring batch 学习笔记

一、Spring batch 简介

Spring batch 是Spring系列处理批量数据的框架。

主要构成如图所示:

https://upload-images.jianshu.io/upload_images/4720632-6fc9ccc6ecb8ab14.png

  • JobRepository  用来注册job的容器,用来存储 Job 在运行过程中的状态信息,如果失败了,可以重失败的地方重新发起。
  • JobLauncher    用来启动Job的接口,可以不指定。会在项目启动时,会直接启动批量逻辑,可以再YML文件里配置。
  • Job                   实际执行的任务,包含一个或多个Step
  • Step                  step包含ItemReaderItemProcessorItemWriter
  • ItemReader      用来读取数据的接口
  • ItemProcessor  用来处理数据的接口
  • ItemWriter         用来输出数据的接口

二、Spring batch工程构建

1.创建工程引入Spring batch

博主用的是Maven的构建的工程,其依赖关系如下

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-batch</artifactId>
</dependency>

另外还有些其他依赖包,根据需要自行引入

2.构建spring batch的环境

spring batch需要数据库中的一些表关系作为数据导入导出的记录,所以首先需要创建一些表来支持Spring batch。sql脚本jar里是自带的,在spring-batch-core-4.1.2.RELEASE-sources.jar 包下的org.springframework.batch.core包下,如图:

博主用的是Mysql数据库,所以运行schema-mysql.sql文件中的脚本即可。

3.数据文件

文件内容如下:

并根据文件数据创建对应的表结构,sql如下:

create table person (
	id varchar(128) comment 'ID' PRIMARY KEY ,
	name varchar(64) comment '姓名',
	age int(3) comment '年龄'
)

4.Demo

1.首先创建pojo类,代码如下:

package com.example.pojo;

public class Person {
    private String id;
    private String name;
    private int age;
	
    //get set方法自行补充
}

2.创建ItemProcessor(中间器),一般用于对数据的校验或数据导入后的加工,代码如下:

package com.example.batch.process;

import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;

import com.example.pojo.Person;

/**
 * 中间器
 * 用于处理读取数之后对数据初步加工,或对数据进行校验的类
 * @author Administrator
 *
 */
public class PersonProcess implements ItemProcessor<Person, Person>{
	
	private static final Logger log = LoggerFactory.getLogger(PersonProcess.class);
	
	@Override
	public Person process(Person person) throws Exception {
		person.setId(UUID.randomUUID().toString());
		return person;
	}

}

以上代码逻辑仅仅是为Person插入加入了一个主键。

3.创建监听器(也可不创建),该类主要用于统计的作用。代码如下:

package com.example.batch.listener;

import org.slf4j.Logger;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * job启动结束监听器
 * @author Administrator
 *
 */
@Component
public class PersonBatchListener extends JobExecutionListenerSupport{
	
    private Logger log = LoggerFactory.getLogger(PersonBatchListener.class);
    
    @Autowired
    public PersonBatchListener() {
		
    }
	
    @Override
    public void afterJob(JobExecution jobExecution) {
        log.info("启动后");
    }

    @Override
    public void beforeJob(JobExecution jobExecution) {
        log.info("启动前");
    }
	
}

这里什么逻辑都没有处理,仅仅作一个展示的作用。可以根据具体要求,加入对应的逻辑。

4.创建spring batch 主要功能的bean,代码如下:

package com.example.batch;

import javax.sql.DataSource;

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.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;

import com.example.batch.listener.PersonBatchListener;
import com.example.batch.process.PersonProcess;
import com.example.pojo.Person;

/**
 * 单个文件批量入库
 * @author Administrator
 *
 */
@Configuration
@EnableBatchProcessing
public class BatchFilePersonConfiguration {
	
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
	
    //单个文件输入流
    @Bean
    @StepScope
    public FlatFileItemReader<Person> faltFileReader() {
        DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
        String[] mateData = {"name","age"};//数据源格式
        tokenizer.setNames(mateData);
        
        DefaultLineMapper<Person> lineMapper = new DefaultLineMapper<Person>();
        lineMapper.setLineTokenizer(tokenizer);
        lineMapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
            setTargetType(Person.class);
        }});
        lineMapper.afterPropertiesSet();
        
        FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
        reader.setLinesToSkip(1);//设置文件开头跳过的行数
        reader.setLineMapper(lineMapper);//映射模型
        reader.setResource(new FileSystemResource("C:/Users/Administrator/Desktop/person.csv"));
        return reader;
    }
	
    //中间器
    @Bean
    public PersonProcess personProcess() {
        return new PersonProcess();
    }
		
    //数据输出流
    @Bean
    public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<Person>()
            .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
            .sql("insert into person (id,name,age) VALUES (:id, :name, :age)")
            .dataSource(dataSource)
            .build();
    }
	
    //job
    @Bean("personJob")
    public Job termStatusJob(JdbcBatchItemWriter<Person> writer,PersonBatchListener listener) {
        Step step = stepBuilderFactory.get("PersonJob")
            .<Person, Person> chunk(1000)//多大数量提交一次
            .reader(this.faltFileReader())
	        .processor(this.personProcess())
	        .writer(writer)
	        .build();
		
        return jobBuilderFactory.get("PersonJob")
            .incrementer(new RunIdIncrementer()).listener(listener).flow(step).end().build();
    }
	
}

大致上可以分为几个Bean,输入流,输出流,step,job。最终目的是得到一个包含step的job,那么step中需要包含输入输出流。

@EnableBatchProcessing注解可以自行创建Spring batch所需要的Bean,方便用时可以直接从容器中取出使用,可以加在该类上也可以加在启动类上。

到此为止,Spring batch的工程就已经开发完成。如果工程运行不了,请检查数据库的相关信息是否配置。以下是我的配置。

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://************:3306/******?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=****
spring.datasource.password=****

有时候,我们并不想项目启动时就自行开始运行批量,而是在确定某种情况的时候才去调用,这时候,我们只需要在配置文件中加入:

#批量程序启动时 是否自动启动JOB
spring.batch.job.enabled=false

然后写一个controller类,调用即可,代码如下:

@RestController
public class ImportContorller {
	
    @Resource(name="personJob")
    private Job personJob;
	
    @Autowired
    private JobLauncher jobLauncher;
	
    @GetMapping("/importData")	
    public String importData() throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {
        JobExecution run1 = jobLauncher.run(personJob, new JobParametersBuilder().addLong("time",System.currentTimeMillis()).toJobParameters());
        run1.getId();
        return "处理成功";
    }
	
}

然后启动项目 访问对应的地址 即可完成Spring batch的批量导入。

5.数据源是数据库时

批量导入数据,一般有两种情况,①从文件中取数导入,②从数据库中取数导入。那么以上的例子是从文件中导入到数据库中的,那么以下我们就介绍如何从数据库中取数。其实实现思路很简单,只需要修改itemReader就可以了,将FlatFileItemReader替换掉就可以了,代码如下

@Bean
public JdbcCursorItemReader<Person> jdbcReader(DataSource dataSource) {
	BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<Person>(Person.class);
		
	JdbcCursorItemReader<Person> reader = new JdbcCursorItemReader<Person>();
	reader.setSql("select name,age from person");
	reader.setRowMapper(mapper);
	reader.setDataSource(dataSource);
	return reader;
}

将step中的FlatFileItemReader<Person> 替换成JdbcCursorItemReader<Person>就可以实现了,当然,如果两个都有需要可以同时共存。

6.多文件导入

有时候,提供的并不是一个完整的文件,而是分为很多很多小容量的文件,那么我们又不想分为多个job去处理,那么Spring 也为我们提供了一种reader可以实现。注意,这里的多文件导入指的是多个相同结构的文件,不能是多个结构不同的文件。否则映射模型会有问题。代码如下:

//多文件路径读写流,要求多个文件的格式是一样的
@Bean
@StepScope
public MultiResourceItemReader<Person> MutipleResourceItemReaderDemo() {
    	
    //设置数据源
    Resource[] resources = new Resource[2];
    resources[0]  = new FileSystemResource("C:/Users/Administrator/Desktop/person.csv");
    resources[1]  = new FileSystemResource("C:/Users/Administrator/Desktop/person2.csv");
    	
    //创建多文件输出流
    MultiResourceItemReader<Person> reader = new MultiResourceItemReader<Person>();
    reader.setDelegate(flatFileItemReader);
    reader.setResources(resources);
    return reader;
}

在原来的基础上FlatFileItemReader<Person> 再增加一个MultiResourceItemReader<Person> 来实现多文件的导入。

7.输出到文件中

将读取到的文件输出到文件中,首先替换到写流,代码如下:

@Bean
public FlatFileItemWriter<Person> fileWriter() {
	//设置文件格式	
	MyFileLineAggregator<Person> lineAggregator = new MyFileLineAggregator<Person>();
		
	FlatFileItemWriter<Person> writer = new FlatFileItemWriter<Person>();
	writer.setResource(new FileSystemResource("C:/Users/Administrator/Desktop/writer.csv"));
	writer.setLineAggregator(lineAggregator);
	return writer;
}

其中lineAggregator  需要实现LineAggregator<T>接口,我是这么写的

package com.example.batch.lineAggregator;

import org.springframework.batch.item.file.transform.LineAggregator;

public class MyFileLineAggregator<T> implements LineAggregator<T>{
	
	private String sparate = "|";//数据间分隔符
	
	@Override
	public String aggregate(T person) {
		return person.toString().replace(",", sparate);
	}
	
	public void setSparate(String sparate) {
		this.sparate = sparate;
	}
}

这里我有一个问题尚未解决,就是输出的文件格式只能有pojo类里的toString()方法来决定,并想到有什么好的方法去修改文件格式。

猜你喜欢

转载自blog.csdn.net/notMoonHeart/article/details/89514780