在Spring Batch中输入不同数据结构格式的多个文件

时间:2016-12-13 08:59:26

标签: spring-batch

根据我的研究,我知道Spring Batch提供API来处理许多不同类型的数据文件格式。

但我需要澄清我们如何在一个chunk / Tasklet中提供不同格式的多个文件。

为此,我知道MultiResourceItemReader可以处理多个文件,但AFAIK所有文件必须具有相同的格式和数据结构。

所以,问题是我们如何在Tasklet中提供多个不同数据格式的文件作为输入?

2 个答案:

答案 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
    }
}
祝你好运: - )