My linux world » JAVA SPRING – Batch

JAVA SPRING - Batch


Here is a short example about Spring Batch that show how to convert a csv file to an xml file.

Contents

Prerequistes

Configuration

If you use maven, add this to your pom.xml

<dependencies>
(...)
 
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
  </dependency>
 
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
  </dependency>
 
(...)
</dependencies>

Buisness

Buisness team have the following csv file named users.csv :

10,John,Doe
20,Jane,Doe
30,Albert,Einstein

We can see that :

Implementation

Model

Let’s deal with the following implementation :

import javax.xml.bind.annotation.XmlRootElement;
 
import lombok.Data;
 
@Data
@XmlRootElement(name = "user")
public class User {
 
    private Integer id;
    private String lastName;
    private String firstName;
 
}

Batch Processor

The batch processor allows you to make any operation about your model.
For example, you can uppercase all last names.
You have to return a new model object, so this batch processor is immutable).

In the example we only diplay the processed user in the LOGGER::info appender.

import org.springframework.batch.item.ItemProcessor;
 
import lombok.extern.slf4j.Slf4j;
 
@Slf4j
public class UserItemProcessor implements ItemProcessor<User, User> {
 
	@Override
	public User process(final User user) throws Exception {
		LOGGER.info("User processed : {}", user);
		return user;
	}
}

JobExecution Listener

You can implement a listener to handle ‘before’ and ‘after’ jobs execution.

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.stereotype.Component;
 
import lombok.extern.slf4j.Slf4j;
 
@Component
@Slf4j
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {
 
	@Override
	public void beforeJob(JobExecution jobExecution) {		
		LOGGER.info("Before job process...");
	}
 
	@Override
	public void afterJob(JobExecution jobExecution) {
		LOGGER.info("After job process...");
		if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
			LOGGER.info("Job completed.");
		}
	}
}

Configuration

Now let’s configure our batch :

 
import java.io.File;
 
import javax.inject.Inject;
 
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.xml.StaxEventItemWriter;
import org.springframework.batch.item.xml.builder.StaxEventItemWriterBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
 
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
 
	@Inject
	public JobBuilderFactory jobBuilderFactory;
 
	@Inject
	public StepBuilderFactory stepBuilderFactory;
 
	/**
	 * Read csv file.
	 * @return FlatFileItemReader
	 */
	@Bean
	public FlatFileItemReader<User> reader() {
 
		BeanWrapperFieldSetMapper<User> bean = new BeanWrapperFieldSetMapper<>();
		bean.setTargetType(User.class);
 
		return new FlatFileItemReaderBuilder<User>()
				.name("userItemReader")
				.resource(new ClassPathResource("users.csv"))
				.delimited()
				.names(new String[]{"id" , "firstName", "lastName"})
				.fieldSetMapper(bean)
				.build();
	}
 
	@Bean
	public UserItemProcessor processor() {
		return new UserItemProcessor();
	}
 
	@Bean
	public Marshaller marshaller() {		
		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
 
		marshaller.setClassesToBeBound(User.class);
 
		return marshaller;
	}	
 
	/**
	 * Write result to xml file.
	 * @return StaxEventItemWriter
	 */
	@Bean
	public StaxEventItemWriter<User> writer() {
		return new StaxEventItemWriterBuilder<User>()
				.name("userItemWriter")
				.resource(new FileSystemResource(new File(TEMP_DIR,"users.xml")))
				.marshaller(marshaller())
				.rootTagName("users")
				.build();
	}
 
	@Bean
	public Job csvToXmlJob() {
 
		return jobBuilderFactory
				.get("csvToXmlJob")
				.flow(step1())
				.end()
				.build();
	}
 
	@Bean
	public Step step1() {
		return stepBuilderFactory
				.get("step1")
				.<User, User>chunk(10)
				.reader(reader())
				.writer(writer())
				.processor(processor())
				.build();
	}
}

Run Job

To run the job, you can do like this :

@Component
public final class JobComponent {
 
	@Inject
	JobLauncher jobLauncher;
 
	@Inject
	Job job;
 
	@RequestMapping("/runMyJob")
	public void runMyJob() throws Exception {
		jobLauncher.run(job, new JobParameters());
	}
 
}

Result

<?xml version="1.0" encoding="UTF-8"?>
<users>
	<user>
		<id>10</id>
		<lastName>Doe</lastName>
		<firstName>John</firstName>
	</user>
	<user>
		<id>20</id>
		<lastName>Doe</lastName>
		<firstName>Jane</firstName>
	</user>
	<user>
		<id>30</id>
		<lastName>Einstein</lastName>
		<firstName>Albert</firstName>
	</user>
</users>

Copyright © 2023 My linux world - by Marc RABAHI
Design by Marc RABAHI and encelades.

Fork me on GitHub