Introducción a Spring Batch

1. Introducción

En este artículo, nos centraremos en una introducción práctica y centrada en el código de Spring Batch. Spring Batch es un marco de procesamiento diseñado para la ejecución robusta de trabajos.

Es la versión actual 3.0, que admite Spring 4 y Java 8. También se adapta a JSR-352, que es una nueva especificación de Java para el procesamiento por lotes.

A continuación, se muestran algunos casos de uso prácticos e interesantes del marco.

2. Conceptos básicos del flujo de trabajo

Spring Batch sigue la arquitectura tradicional por lotes donde un repositorio de trabajos hace el trabajo de programar e interactuar con el trabajo.

Un trabajo puede tener más de un paso, y cada paso normalmente sigue la secuencia de lectura de datos, procesamiento y escritura.

Y, por supuesto, el marco hará la mayor parte del trabajo pesado por nosotros aquí, especialmente cuando se trata del trabajo de persistencia de bajo nivel de tratar con los trabajos, usando sqlite para el repositorio de trabajos.

2.1. Nuestro ejemplo de caso de uso

El caso de uso simple que vamos a abordar aquí es: vamos a migrar algunos datos de transacciones financieras de CSV a XML.

El archivo de entrada tiene una estructura muy simple: contiene una transacción por línea, compuesta por: un nombre de usuario, la identificación del usuario, la fecha de la transacción y el monto:

username, userid, transaction_date, transaction_amount devendra, 1234, 31/10/2015, 10000 john, 2134, 3/12/2015, 12321 robin, 2134, 2/02/2015, 23411

3. El Maven POM

Las dependencias requeridas para este proyecto son spring core, spring batch y sqlite jdbc connector:

   org.xerial sqlite-jdbc 3.15.1   org.springframework spring-oxm 5.2.0.RELEASE   org.springframework spring-jdbc 5.2.0.RELEASE   org.springframework.batch spring-batch-core 4.2.0.RELEASE 

4. Configuración de lote de primavera

Lo primero que haremos es configurar Spring Batch con XML:

Por supuesto, también está disponible una configuración de Java:

@Configuration @EnableBatchProcessing public class SpringConfig { @Value("org/springframework/batch/core/schema-drop-sqlite.sql") private Resource dropReopsitoryTables; @Value("org/springframework/batch/core/schema-sqlite.sql") private Resource dataReopsitorySchema; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.sqlite.JDBC"); dataSource.setUrl("jdbc:sqlite:repository.sqlite"); return dataSource; } @Bean public DataSourceInitializer dataSourceInitializer(DataSource dataSource) throws MalformedURLException { ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); databasePopulator.addScript(dropReopsitoryTables); databasePopulator.addScript(dataReopsitorySchema); databasePopulator.setIgnoreFailedDrops(true); DataSourceInitializer initializer = new DataSourceInitializer(); initializer.setDataSource(dataSource); initializer.setDatabasePopulator(databasePopulator); return initializer; } private JobRepository getJobRepository() throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(dataSource()); factory.setTransactionManager(getTransactionManager()); factory.afterPropertiesSet(); return (JobRepository) factory.getObject(); } private PlatformTransactionManager getTransactionManager() { return new ResourcelessTransactionManager(); } public JobLauncher getJobLauncher() throws Exception { SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); jobLauncher.setJobRepository(getJobRepository()); jobLauncher.afterPropertiesSet(); return jobLauncher; } }

5. Configuración de trabajo por lotes de primavera

Escribamos ahora nuestra descripción de trabajo para el trabajo de CSV a XML:

                           com.baeldung.spring_batch_intro.model.Transaction           

Y, por supuesto, la configuración de trabajo similar basada en Java:

public class SpringBatchConfig { @Autowired private JobBuilderFactory jobs; @Autowired private StepBuilderFactory steps; @Value("input/record.csv") private Resource inputCsv; @Value("file:xml/output.xml") private Resource outputXml; @Bean public ItemReader itemReader() throws UnexpectedInputException, ParseException { FlatFileItemReader reader = new FlatFileItemReader(); DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); String[] tokens = { "username", "userid", "transactiondate", "amount" }; tokenizer.setNames(tokens); reader.setResource(inputCsv); DefaultLineMapper lineMapper = new DefaultLineMapper(); lineMapper.setLineTokenizer(tokenizer); lineMapper.setFieldSetMapper(new RecordFieldSetMapper()); reader.setLineMapper(lineMapper); return reader; } @Bean public ItemProcessor itemProcessor() { return new CustomItemProcessor(); } @Bean public ItemWriter itemWriter(Marshaller marshaller) throws MalformedURLException { StaxEventItemWriter itemWriter = new StaxEventItemWriter(); itemWriter.setMarshaller(marshaller); itemWriter.setRootTagName("transactionRecord"); itemWriter.setResource(outputXml); return itemWriter; } @Bean public Marshaller marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(new Class[] { Transaction.class }); return marshaller; } @Bean protected Step step1(ItemReader reader, ItemProcessor processor, ItemWriter writer) { return steps.get("step1"). chunk(10) .reader(reader).processor(processor).writer(writer).build(); } @Bean(name = "firstBatchJob") public Job job(@Qualifier("step1") Step step1) { return jobs.get("firstBatchJob").start(step1).build(); } }

Bien, ahora que tenemos la configuración completa, vamos a analizarla y empezar a discutirla.

5.1. Leer datos y crear objetos con ItemReader

Primero configuramos el cvsFileItemReader que leerá los datos del record.csv y los convertirá en el objeto Transaction :

@SuppressWarnings("restriction") @XmlRootElement(name = "transactionRecord") public class Transaction { private String username; private int userId; private LocalDateTime transactionDate; private double amount; /* getters and setters for the attributes */ @Override public String toString() { return "Transaction [username=" + username + ", userId=" + userId + ", transactionDate=" + transactionDate + ", amount=" + amount + "]"; } }

Para hacerlo, utiliza un mapeador personalizado:

public class RecordFieldSetMapper implements FieldSetMapper { public Transaction mapFieldSet(FieldSet fieldSet) throws BindException { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yyy"); Transaction transaction = new Transaction(); transaction.setUsername(fieldSet.readString("username")); transaction.setUserId(fieldSet.readInt(1)); transaction.setAmount(fieldSet.readDouble(3)); String dateString = fieldSet.readString(2); transaction.setTransactionDate(LocalDate.parse(dateString, formatter).atStartOfDay()); return transaction; } }

5.2. Procesamiento de datos con ItemProcessor

Hemos creado nuestro propio procesador de artículos, CustomItemProcessor . Esto no procesa nada relacionado con el objeto de transacción; todo lo que hace es pasar el objeto original proveniente del lector al escritor:

public class CustomItemProcessor implements ItemProcessor { public Transaction process(Transaction item) { return item; } }

5.3. Writing Objects to the FS With ItemWriter

Finally, we are going to store this transaction into an xml file located at xml/output.xml:

5.4. Configuring the Batch Job

So all we have to do is connect the dots with a job – using the batch:job syntax.

Note the commit-interval – that's the number of transactions to be kept in memory before committing the batch to the itemWriter; it will hold the transactions in memory until that point (or until the end of the input data is encountered):

5.5. Running the Batch Job

That's it – let's now set up and run everything:

public class App { public static void main(String[] args) { // Spring Java config AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SpringConfig.class); context.register(SpringBatchConfig.class); context.refresh(); JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher"); Job job = (Job) context.getBean("firstBatchJob"); System.out.println("Starting the batch job"); try { JobExecution execution = jobLauncher.run(job, new JobParameters()); System.out.println("Job Status : " + execution.getStatus()); System.out.println("Job completed"); } catch (Exception e) { e.printStackTrace(); System.out.println("Job failed"); } } }

6. Conclusion

Este tutorial le brinda una idea básica de cómo trabajar con Spring Batch y cómo usarlo en un caso de uso simple.

Muestra cómo puede desarrollar fácilmente su canal de procesamiento por lotes y cómo puede personalizar diferentes etapas de lectura, procesamiento y escritura.

La implementación completa de este tutorial se puede encontrar en el proyecto github; este es un proyecto basado en Eclipse, por lo que debería ser fácil de importar y ejecutar como está.