spring batch提供了对数据校验的功能,先读取数据,然后校验数据,最后写入数据。
在spring batch中实现检验,先定义一个类去实现Validator接口,然后Spring batch提供了一个ValidatingItemProcessor类,用于去执行Validator的接口类。实质ValidatingItemProcessor也是实现了ItemProcessor接口,执行校验就相当于执行一次数据处理。
示例:从下面文件中reader数据,然后process,最后进行writer数据。在进行process时又分了两个子process进行数据校验和处理。在进行process时先检验数据,name中手否包含”l”和”t”字符,合法的数据把name转化为大写。
ID,NAME,AGE
1,lzj,28
2,tom,20
3,terry,30
4,lerry,18
5,bob,25
6,linda,27
7,marry,39
8,long,22
9,kin,33
10,jiken,40
在进行配置job程序时,首先创建每条数据对应的bean和创建一个校验器。
public class User implements Serializable{
private String id;
private String name;
private String age;
……省略set/get和toString方法
}
创建校验器,实现spring batch的的校验接口即可
import org.springframework.batch.item.validator.ValidationException;
import org.springframework.batch.item.validator.Validator;
/*该检验器只校验name中含有"l"和"t"的数据行*/
public class UserValidator implements Validator<User>{
@Override
public void validate(User user) throws ValidationException {
if (!user.getName().contains("l") && !user.getName().contains("t")) {
throw new ValidationException("参数不合法!");
}
}
}
配置该job作业如下:
import java.util.ArrayList;
import java.util.List;
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.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.LineMapper;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.LineTokenizer;
import org.springframework.batch.item.support.CompositeItemProcessor;
import org.springframework.batch.item.validator.ValidatingItemProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.validation.BindException;
@Configuration
public class BatchConfig {
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Autowired
private JobBuilderFactory jobBuilderFactory;
/*1、创建一个Job作业*/
@Bean
public Job fileReaderJob() throws Exception{
return jobBuilderFactory.get("fileReaderJob")
.start(chunkStep())
.build();
}
//2、创建一个step*/
@Bean
public Step chunkStep() throws Exception{
return stepBuilderFactory.get("chunkStep")
.<User, User>chunk(2) //每4次提交一次
.reader(fileItemReader()) //读取文件,并把文件中每行数据映射到工程中的User bean中
.processor(itemProcess())
.writer(list -> list.forEach(System.out::println))
.allowStartIfComplete(true)
.build();
}
//3、配置要读取文件的特性*/
@Bean
public ItemReader<User> fileItemReader(){
FlatFileItemReader<User> reader= new FlatFileItemReader<>();
reader.setResource(new ClassPathResource("/data/User.txt")); //指定读取文件的位置
reader.setLinesToSkip(1); //文件的第一行不用读取,跳过第一行
reader.setLineMapper(userLineMapper()); //设置lineMapper,用于把文件中的每行数据映射到工程的bean中
return reader;
}
//4、定义一个LineMapper,用于把文件中的每行数据映射到工程的bean中*/
private LineMapper<User> userLineMapper(){
DefaultLineMapper<User> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(userLineTokenizer()); //设置文件的解析器
lineMapper.setFieldSetMapper(new UserFieldStepMapper()); //设置文件的映射器
lineMapper.afterPropertiesSet(); //完成文件的配置
return lineMapper;
}
//5、定义文件的解析器,解析文件中的ID、NAME、AGE三列*/
private LineTokenizer userLineTokenizer(){
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setNames(new String[]{"ID", "NAME", "AGE"});
return tokenizer;
}
//6、实现FieldSetMapper,用于从User.txt文件中读取数据,用读取的数据创建工程中的User对象*/
private static class UserFieldStepMapper implements FieldSetMapper<User>{
/*一个fieldSed对应文件中的一行数据*/
@Override
public User mapFieldSet(FieldSet fieldSet) throws BindException {
// TODO Auto-generated method stub
return new User(fieldSet.readString("ID"),
fieldSet.readString("NAME"),
fieldSet.readString("AGE"));
}
}
/*创建process: process中共包括两个process,userValidateItemProcess和UserProcess*/
@Bean
public ItemProcessor<User, User> itemProcess() throws Exception{
List<ItemProcessor<User, User>> list = new ArrayList<>();
ValidatingItemProcessor<User> userValidateItemProcess =
new ValidatingItemProcessor<>(new UserValidator());
userValidateItemProcess.setFilter(false); //设置false,说明不过滤,当校验出错时,直接抛出异常,不再继续执行下去
list.add(userValidateItemProcess); //首先执行userValidateItemProcess
list.add(new UserProcess()); //然后执new UserProcess()。执行process是有顺序的,先放入list的先执行,这一点从源码中可以看出。
CompositeItemProcessor<User, User> compositeItemProcessor =
new CompositeItemProcessor<>();
compositeItemProcessor.setDelegates(list);
compositeItemProcessor.afterPropertiesSet(); //对list校验是否为空
return compositeItemProcessor;
}
/*该process把姓名由小写变为大写*/
private class UserProcess implements ItemProcessor<User, User>{
@Override
public User process(User item) throws Exception {
return new User(item.getId(), item.getName().toUpperCase(), item.getAge());
}
}
}
运行该job,输出内容如下:
……省略日志
User [id=1, name=LZJ, age=28]
User [id=2, name=TOM, age=20]
User [id=3, name=TERRY, age=30]
User [id=4, name=LERRY, age=18]
2018-03-10 18:39:13.495 ERROR 10304 --- [ main] o.s.batch.core.step.AbstractStep : Encountered an error executing step chunkStep in job fileReaderJob
org.springframework.batch.item.validator.ValidationException: 参数不合法!
……
从输出内容看,前4条检验成功,并把name由小写变为大写。校验到第5条时,抛出异常,job终止,不再继续执行。
如果把userValidateItemProcess.setFilter(true); 表示过滤,校验失败后过滤出去,继续向下执行。重新运行该Job,输出内容如下:
结果只写入如下两条数据:
……
User [id=6, name=LINDA, age=27]
User [id=8, name=LONG, age=22]
……
是因为上一次在第5条数据失败终止,现在重新启动从失败出重新执行,结果把后面满足条件的两条数据输出来。如果在setFilter(true)条件下再重新运行一次的话,就会输出全部满足校验的数据了:
……
User [id=1, name=LZJ, age=28]
User [id=2, name=TOM, age=20]
User [id=3, name=TERRY, age=30]
User [id=4, name=LERRY, age=18]
User [id=6, name=LINDA, age=27]
User [id=8, name=LONG, age=22]
……