根据我的研究,我知道Spring Batch提供API来处理许多不同类型的数据文件格式。
但我需要澄清我们如何在一个chunk / Tasklet中提供不同格式的多个文件。
为此,我知道MultiResourceItemReader可以处理多个文件,但AFAIK所有文件必须具有相同的格式和数据结构。
所以,问题是我们如何在Tasklet中提供多个不同数据格式的文件作为输入?
答案 0 :(得分:0)
我不认为有多种输入格式的开箱即用的Spring批量阅读器。
你必须建立自己的。当然,您可以在自定义文件阅读器中重用现有的FileItemReader
作为代理,对于每种文件类型/格式,请使用正确的文件。
答案 1 :(得分:0)
Asoub是对的,没有开箱即用的Spring Batch阅读器“全部读取!”。但是,只需要一些相当简单和直接的类,您就可以创建一个java配置弹簧批处理应用程序,它将使用不同文件格式的不同文件。
对于我的一个应用程序,我有一个类似的用例,我写了一堆相当简单直接的实现和Spring Batch框架的扩展来创建我称之为“通用”的读者。所以回答你的问题:下面你会发现我使用弹簧批处理不同类型文件格式的代码。显然,下面你会发现剥离的实现,但它应该让你朝着正确的方向前进。
一行由记录表示:
public class Record {
private Object[] columns;
public void setColumnByIndex(Object candidate, int index) {
columns[index] = candidate;
}
public Object getColumnByIndex(int index){
return columns[index];
}
public void setColumns(Object[] columns) {
this.columns = columns;
}
}
每行包含多个列,列由分隔符分隔。 file1是否包含10列和/或file2是否只包含3列无关紧要。
以下读者只需将每一行映射到记录:
@Component
public class GenericReader {
@Autowired
private GenericLineMapper genericLineMapper;
@SuppressWarnings({ "unchecked", "rawtypes" })
public FlatFileItemReader reader(File file) {
FlatFileItemReader<Record> reader = new FlatFileItemReader();
reader.setResource(new FileSystemResource(file));
reader.setLineMapper((LineMapper) genericLineMapper.defaultLineMapper());
return reader;
}
}
映射器占用一行并将其转换为对象数组:
@Component
public class GenericLineMapper {
@Autowired
private ApplicationConfiguration applicationConfiguration;
@SuppressWarnings({ "unchecked", "rawtypes" })
public DefaultLineMapper defaultLineMapper() {
DefaultLineMapper lineMapper = new DefaultLineMapper();
lineMapper.setLineTokenizer(tokenizer());
lineMapper.setFieldSetMapper(new CustomFieldSetMapper());
return lineMapper;
}
private DelimitedLineTokenizer tokenizer() {
DelimitedLineTokenizer tokenize = new DelimitedLineTokenizer();
tokenize.setDelimiter(Character.toString(applicationConfiguration.getDelimiter()));
tokenize.setQuoteCharacter(applicationConfiguration.getQuote());
return tokenize;
}
}
将列转换为记录的“魔力”发生在FieldSetMapper:
中@Component
public class CustomFieldSetMapper implements FieldSetMapper<Record> {
@Override
public Record mapFieldSet(FieldSet fieldSet) throws BindException {
Record record = new Record();
Object[] row = new Object[fieldSet.getValues().length];
for (int i = 0; i < fieldSet.getValues().length; i++) {
row[i] = fieldSet.getValues()[i];
}
record.setColumns(row);
return record;
}
}
使用yaml配置,用户提供输入目录和文件名列表,并且如果列包含分隔符,则引用相应的分隔符和字符以引用列。这是一个这样的yaml配置的例子:
@Component
@ConfigurationProperties
public class ApplicationConfiguration {
private String inputDir;
private List<String> fileNames;
private char delimiter;
private char quote;
// getters and setters ommitted
}
然后是application.yml:
input-dir: src/main/resources/
file-names: [yourfile1.csv, yourfile2.csv, yourfile3.csv]
delimiter: "|"
quote: "\""
最后但并非最不重要的是,把它们放在一起:
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Autowired
private GenericReader genericReader;
@Autowired
private NoOpWriter noOpWriter;
@Autowired
private ApplicationConfiguration applicationConfiguration;
@Bean
public Job yourJobName() {
List<Step> steps = new ArrayList<>();
applicationConfiguration.getFileNames().forEach(f -> steps.add(loadStep(new File(applicationConfiguration.getInputDir() + f))));
return jobBuilderFactory.get("yourjobName")
.start(createParallelFlow(steps))
.end()
.build();
}
@SuppressWarnings("unchecked")
public Step loadStep(File file) {
return stepBuilderFactory.get("step-" + file.getName())
.<Record, Record> chunk(10)
.reader(genericReader.reader(file))
.writer(noOpWriter)
.build();
}
private Flow createParallelFlow(List<Step> steps) {
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
// max multithreading = -1, no multithreading = 1, smart size = steps.size()
taskExecutor.setConcurrencyLimit(1);
List<Flow> flows = steps.stream()
.map(step -> new FlowBuilder<Flow>("flow_" + step.getName()).start(step).build())
.collect(Collectors.toList());
return new FlowBuilder<SimpleFlow>("parallelStepsFlow")
.split(taskExecutor)
.add(flows.toArray(new Flow[flows.size()]))
.build();
}
}
出于演示目的,您可以将所有类放在一个包中。 NoOpWriter只记录我的测试文件的第二列。
@Component
public class NoOpWriter implements ItemWriter<Record> {
@Override
public void write(List<? extends Record> items) throws Exception {
items.forEach(i -> System.out.println(i.getColumnByIndex(1)));
// NO - OP
}
}
祝你好运: - )