Lote de Primavera usando o Particionador
1. Visão geral
Em nossointroduction to Spring Batch anterior, apresentamos a estrutura como uma ferramenta de processamento em lote. Também exploramos os detalhes de configuração e a implementação para uma execução de tarefa de processo único e com thread único.
Para implementar um trabalho com algum processamento paralelo, é fornecida uma variedade de opções. Em um nível superior, existem dois modos de processamento paralelo:
-
Processo único, multithread
-
Multi-Processo
Neste artigo rápido, discutiremos o particionamento deStep, que pode ser implementado para jobs de processo único e multiprocessos.
2. Particionando uma Etapa
O Spring Batch com particionamento nos fornece a facilidade de dividir a execução de umStep:
A imagem acima mostra uma implementação deJob comStep particionado.
Existe umStep chamado "Mestre", cuja execução é dividida em algumas etapas "Escravo". Esses escravos podem tomar o lugar de um mestre, e o resultado ainda será inalterado. Tanto o mestre quanto o escravo são instâncias deStep. Os escravos podem ser serviços remotos ou apenas executar threads localmente.
Se necessário, podemos passar dados do mestre para o escravo. Os metadados (ou seja, oJobRepository), garante que cada escravo seja executado apenas uma vez em uma única execução deJob.
Aqui está o diagrama de sequência que mostra como tudo funciona:
Conforme mostrado, oPartitionStep está conduzindo a execução. OPartitionHandler é responsável por dividir o trabalho de “Mestre” em “Escravos”. OStep mais à direita é o escravo.
3. The Maven POM
As dependências do Maven são as mesmas mencionadas em nossoarticle anterior. Ou seja, Spring Core, Spring Batch e a dependência do banco de dados (em nosso caso,SQLite).
4. Configuração
Em nossoarticle introdutório, vimos um exemplo de conversão de alguns dados financeiros de arquivo CSV para XML. Vamos estender o mesmo exemplo.
Aqui, converteremos as informações financeiras de 5 arquivos CSV em arquivos XML correspondentes, usando uma implementação multithread.
Podemos conseguir isso usando um único particionamentoJobeStep. Teremos cinco threads, um para cada um dos arquivos CSV.
Primeiro de tudo, vamos criar um trabalho:
@Bean(name = "partitionerJob")
public Job partitionerJob()
throws UnexpectedInputException, MalformedURLException, ParseException {
return jobs.get("partitioningJob")
.start(partitionStep())
.build();
}
Como podemos ver, esseJob começa comPartitioningStep. Esta é a nossa etapa principal, que será dividida em várias etapas escravas:
@Bean
public Step partitionStep()
throws UnexpectedInputException, MalformedURLException, ParseException {
return steps.get("partitionStep")
.partitioner("slaveStep", partitioner())
.step(slaveStep())
.taskExecutor(taskExecutor())
.build();
}
Aqui, vamos criar oPartitioningStep using the StepBuilderFactory. Para isso, precisamos fornecer as informações sobreSlaveStepsePartitioner.
OPartitioner é uma interface que fornece a facilidade de definir um conjunto de valores de entrada para cada um dos escravos. Em outras palavras, a lógica para dividir tarefas nos respectivos encadeamentos aqui.
Vamos criar uma implementação dele, chamadaCustomMultiResourcePartitioner, onde colocaremos os nomes dos arquivos de entrada e saída emExecutionContext para passar para cada etapa do escravo:
public class CustomMultiResourcePartitioner implements Partitioner {
@Override
public Map partition(int gridSize) {
Map map = new HashMap<>(gridSize);
int i = 0, k = 1;
for (Resource resource : resources) {
ExecutionContext context = new ExecutionContext();
Assert.state(resource.exists(), "Resource does not exist: "
+ resource);
context.putString(keyName, resource.getFilename());
context.putString("opFileName", "output"+k+++".xml");
map.put(PARTITION_KEY + i, context);
i++;
}
return map;
}
}
Também criaremos o bean para esta classe, onde forneceremos o diretório de origem para os arquivos de entrada:
@Bean
public CustomMultiResourcePartitioner partitioner() {
CustomMultiResourcePartitioner partitioner
= new CustomMultiResourcePartitioner();
Resource[] resources;
try {
resources = resoursePatternResolver
.getResources("file:src/main/resources/input/*.csv");
} catch (IOException e) {
throw new RuntimeException("I/O problems when resolving"
+ " the input file pattern.", e);
}
partitioner.setResources(resources);
return partitioner;
}
Definiremos o passo escravo, como qualquer outro passo com o leitor e o escritor. O leitor e o gravador serão os mesmos que vimos em nosso exemplo introdutório, exceto que eles receberão o parâmetro de nome de arquivo deStepExecutionContext.
Observe que esses beans precisam ter o escopo da etapa para que possam receber os parâmetrosstepExecutionContext, a cada etapa. Se eles não tiverem o escopo da etapa, seus beans serão criados inicialmente e não aceitarão os nomes de arquivo no nível da etapa:
@StepScope
@Bean
public FlatFileItemReader itemReader(
@Value("#{stepExecutionContext[fileName]}") String filename)
throws UnexpectedInputException, ParseException {
FlatFileItemReader reader
= new FlatFileItemReader<>();
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
String[] tokens
= {"username", "userid", "transactiondate", "amount"};
tokenizer.setNames(tokens);
reader.setResource(new ClassPathResource("input/" + filename));
DefaultLineMapper lineMapper
= new DefaultLineMapper<>();
lineMapper.setLineTokenizer(tokenizer);
lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
reader.setLinesToSkip(1);
reader.setLineMapper(lineMapper);
return reader;
}
@Bean
@StepScope
public ItemWriter itemWriter(Marshaller marshaller,
@Value("#{stepExecutionContext[opFileName]}") String filename)
throws MalformedURLException {
StaxEventItemWriter itemWriter
= new StaxEventItemWriter();
itemWriter.setMarshaller(marshaller);
itemWriter.setRootTagName("transactionRecord");
itemWriter.setResource(new ClassPathResource("xml/" + filename));
return itemWriter;
}
Ao mencionar o leitor e o escritor na etapa escrava, podemos passar os argumentos como nulos, pois esses nomes de arquivos não serão usados, pois receberão os nomes de arquivos destepExecutionContext:
@Bean
public Step slaveStep()
throws UnexpectedInputException, MalformedURLException, ParseException {
return steps.get("slaveStep").chunk(1)
.reader(itemReader(null))
.writer(itemWriter(marshaller(), null))
.build();
}
5. Conclusão
Neste tutorial, discutimos como implementar um trabalho com processamento paralelo usando o Spring Batch.
Como sempre, a implementação completa para este exemplo está disponívelover on GitHub.

