Hace tiempo que trabajo con Spring Batch, de echo ahora estoy en un proceso de migración, que conlleva migrar unos procesos.
Esto me lleva a explicar, de manera practica, cómo funciona el desarrollo de una aplicación batch. Empezaré por un pequeño ejemplo, en un repositorio de Github y a medida que vaya creando mas post, explicando cada componente y enfoque para el procesamiento por lotes.
PROCESO ORIENTADO A «CHUNK«
En inglés Chunk Oriented Processing, esta característica fue introducida a partir de Spring Batch v2.0, este enfoque trabaja con bloques transaccionales de items, para el procesamiento por lotes.
Esta característica, me parece muy interesante y una evolución lógica de este framework ligero. Veremos como se configuran los «chunks» y como se comportan en el ejemplo practico.
PARTES DE BÁSICAS DE UNA IMPLEMENTACIÓN CON SPRING BATCH
Las partes principales a las que tenemos acceso en una primera aproximación, serían: job, step, itemReader, itemProcessor, itemWriter.
JOB
El código de abajo, muestra un ejemplo muy simple de una «configuración» de un Job con Spring Batch.
@Bean
public Job job(final JobBuilderFactory jobBuilderFactory, final StepBuilderFactory stepBuilderFactory) {
return jobBuilderFactory.get("loadCsvToDatabaseJob")
.start(loadCsvToDatabase.step(stepBuilderFactory))
.build();
}
Nos centraremos, por ahora, en cómo asignamos los «steps» y el orden de ejecución, más adelante hablaremos de Spring Batch flow. Podemos ver que el primer step, es loadCsvToDatabase, si quisiéramos que después de este step, se ejecutara otro, podríamos .next(step).
STEP
El job, podría decirse que sería el organizador de step. Estos steps, son los que contienen y procesan los datos mediante «chunks«, abajo veremos un ejemplo.
public Step step(StepBuilderFactory stepBuilderFactory) {
return stepBuilderFactory.get("loadCsvToDatabase-step")
.allowStartIfComplete(true)
.<Person, Person>chunk(5)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
Vemos partes interesantes, por ejemplo, la definición de «chunk» <Person,Person>chunk(5). Esto quiere decir, que el proceso se hará de 5 en 5, siempre que sea posible, desde el resultado de «lectura».
Por otro lado, realmente, lo que espera de configuración del step, es READER/WRITER, el proceso es un poco secundario, por eso tenemos <Objecto1,Objecto2>. Objecto1, sería lo que devolvería el READER y Objecto2, lo que espera recibir el WRITER.
STEP – READER
En la implementación que comparo al final del post, es un ejemplo de CSV -> DB
@Bean("loadCsvToDatabaseReader")
public FlatFileItemReader<Person> reader() {
return new FlatFileItemReaderBuilder<Person>()
.name("personItemReader")
.resource(new ClassPathResource("data/person.csv"))
.delimited()
.names("name", "lastName", "email")
.fieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
setTargetType(Person.class);
}})
.build();
}
No entraré en detalles, de como funciona este reader, supongo que más adelante, explicaré su funcionamiento, pero teniendo el ejemplo, podéis jugar con el.
En el resource, le indicamos el resource que contiene el CSV, el delimited es como se delimitan las columnas, en este caso por defecto y la cabecera de las columnas, la magia lo hará fieldSetMapper, el encargado de crear los objetos Person mediante el nombre de la cabecera
STEP – PROCESSOR
@Component("loadCsvToDatabaseProcessor")
@AllArgsConstructor
public class BuildComposeNameProcess implements ItemProcessor<Person, Person> {
@Override
public Person process(final Person person) {
final Person processedPerson = new Person();
processedPerson.setName(person.getName());
processedPerson.setLastName(person.getLastName());
processedPerson.setEmail(person.getEmail());
processedPerson.setComposeName(person.getLastName() + " " +person.getName());
processedPerson.setActive(true);
return processedPerson;
}
}
Ahora toca explicar el processor, como ya he comentado antes, es secundario y en este caso podría ser del tipo mapper.
Lo único relevante es la definición ItemProcessor<Objeto,Objeto>, pero realmente es un maper de reader a writer o si requiere algún tipo de transformación o incluso recuperar datos extra para crear objetos enriquecidos
STEP – WRITER
Por ultimo el writer
@Slf4j
@AllArgsConstructor
@Component("personWriter")
public class PersonWriter implements ItemWriter<Person> {
private PersonRepository personRepository;
@Override
public void write(List<? extends Person> persons) {
persons.stream().map(Person::toString).forEach(log::info);
personRepository.saveAll(persons);
}
}
Lo mismo que el anterior, aúnque este si que es mandatorio para la creacion del step, pero en este ejemplo, poco que contar, entra un Person y se encarga de persistir, en este caso mediante un repositorio.
Hablaremos de los tipos en próximos post. Cualquier cosa, podéis poneros en contacto conmigo, estaré encantado de ayudaros, un saludo
IMPLEMENTACION