一、Spring batch 简介
Spring batch 是Spring系列处理批量数据的框架。
主要构成如图所示:
- JobRepository 用来注册job的容器,用来存储 Job 在运行过程中的状态信息,如果失败了,可以重失败的地方重新发起。
- JobLauncher 用来启动Job的接口,可以不指定。会在项目启动时,会直接启动批量逻辑,可以再YML文件里配置。
- Job 实际执行的任务,包含一个或多个Step
- Step step包含ItemReader、ItemProcessor和ItemWriter
- 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()方法来决定,并想到有什么好的方法去修改文件格式。