In this example, we’ll develop a code which will read from CSV and write it to XML file.
pom.xml — All required dependencies needed to run the App.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>xml-spring-batch-csv-to-xml</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xml-spring-batch-csv-to-xml</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Employee.java
package com.example.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@AllArgsConstructor(staticName = "create")
@NoArgsConstructor
@Data
@Builder
@XmlRootElement(name = "Employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
@XmlElement(name = "empId")
private Long empId;
@XmlElement(name = "firstName")
private String firstName;
@XmlElement(name = "lastName")
private String lastName;
@XmlElement(name = "age")
private Integer age;
@XmlElement(name = "email")
private String email;
@Override
public String toString() {
return empId + "," + firstName + "," + lastName + "," + age + "," + email;
}
}
EmployeeFieldSetMapper.java
package com.example.mapper;
import com.example.model.Employee;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public class EmployeeFieldSetMapper implements FieldSetMapper<Employee> {
@Override
public Employee mapFieldSet(FieldSet fieldSet) throws BindException {
Employee employee = new Employee();
employee.setEmpId(fieldSet.readLong(0));
employee.setFirstName(fieldSet.readString(1));
employee.setLastName(fieldSet.readString(2));
employee.setAge(fieldSet.readInt(3));
employee.setEmail(fieldSet.readString(4));
return employee;
}
}
EmployeeListener.java
package com.example.listener;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
public class EmployeeListener implements JobExecutionListener {
private LocalDateTime startTime, stopTime;
@Override
public void beforeJob(JobExecution jobExecution) {
startTime = LocalDateTime.now();
System.out.println("ExamResult Job starts at :" + startTime);
}
@Override
public void afterJob(JobExecution jobExecution) {
stopTime = LocalDateTime.now();
System.out.println("ExamResult Job stops at :" + stopTime);
System.out.println("Total time take in millis :" + getTimeInMillis(startTime, stopTime));
if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
System.out.println("ExamResult job completed successfully");
//Here you can perform some other business logic like cleanup
} else if (jobExecution.getStatus() == BatchStatus.FAILED) {
System.out.println("ExamResult job failed with following exceptions ");
List<Throwable> exceptionList = jobExecution.getAllFailureExceptions();
for (Throwable th : exceptionList) {
System.err.println("exception :" + th.getLocalizedMessage());
}
}
}
private long getTimeInMillis(LocalDateTime start, LocalDateTime stop) {
return Duration.between(start, stop).toMillis();
}
}
MainApp.java
package com.example;
import com.example.model.Employee;
import com.github.javafaker.Faker;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.LongStream;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class XmlSpringBatchCsvToXmlApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(XmlSpringBatchCsvToXmlApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
extracted();
ApplicationContext context = new ClassPathXmlApplicationContext("spring-batch-context.xml");
JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
Job job = (Job) context.getBean("employeeJob");
try {
JobExecution execution = jobLauncher.run(job, new JobParameters());
System.out.println("Job Exit Status : "+ execution.getStatus());
} catch (JobExecutionException e) {
System.out.println("Job ExamResult failed");
e.printStackTrace();
}
}
private void extracted() {
Faker faker = Faker.instance();
LongStream.rangeClosed(1,101)
.forEach(index -> {
System.out.println(Employee.create(index, faker.name().firstName(), faker.name().lastName(),
ThreadLocalRandom.current().nextInt(1,80),
faker.name().firstName()+"."+ faker.name().lastName()+"@gmail.com"));
});
}
}
employee.csv
empId,firstName,lastName,age,email
1,Jody,Yundt,37,Hang.Dicki@gmail.com
2,Sheena,Gulgowski,29,Dana.Fadel@gmail.com
3,Zenia,Windler,4,Terry.Mosciski@gmail.com
4,Shawnda,Ortiz,6,Olene.Welch@gmail.com
5,Glenn,Langworth,49,Jenelle.Bartell@gmail.com
6,Earlie,Crooks,48,Silva.Gerhold@gmail.com
7,Lance,Lynch,4,Truman.Schamberger@gmail.com
8,Bree,Kub,51,Elmer.Mitchell@gmail.com
9,Cesar,Reilly,32,Danny.Becker@gmail.com
10,Theron,Abernathy,58,Frances.Medhurst@gmail.com
11,Tarra,Thiel,45,Al.Little@gmail.com
12,Zackary,Graham,48,Janet.Zemlak@gmail.com
13,Travis,Bashirian,40,Collin.Effertz@gmail.com
14,Billie,Jacobi,29,George.Rice@gmail.com
15,Jimmie,Wiegand,25,Fletcher.Ondricka@gmail.com
16,Renaldo,Fahey,30,Dominic.Hackett@gmail.com
17,Tasia,Conroy,39,Hector.Rogahn@gmail.com
18,Carroll,Price,57,Rozanne.Renner@gmail.com
19,Chong,Nolan,12,Melvin.Ziemann@gmail.com
20,Britany,Batz,59,Margarete.Kilback@gmail.com
21,Elwanda,Bergstrom,58,Bradley.Reichel@gmail.com
22,Sang,Cronin,58,Claudio.McGlynn@gmail.com
23,Joel,Schroeder,55,Shenika.Thiel@gmail.com
24,Elfrieda,Berge,46,Janina.McCullough@gmail.com
25,Cecile,Schiller,21,Ruthann.Ullrich@gmail.com
26,Antoinette,Quigley,22,Emery.Ondricka@gmail.com
27,Foster,Kuphal,25,Shelby.Nader@gmail.com
28,Erich,Macejkovic,37,Liberty.Dooley@gmail.com
29,Jong,Daugherty,37,Tracey.Stanton@gmail.com
30,Darwin,Yost,22,Hipolito.Hauck@gmail.com
31,Samual,Schinner,1,Maurice.Bogan@gmail.com
32,Wyatt,Schiller,35,Tonda.Dach@gmail.com
33,Marianne,Stracke,74,Marcell.Ferry@gmail.com
34,Charity,Schaefer,63,Susan.Kerluke@gmail.com
35,Tandy,Prohaska,25,Lynwood.Cassin@gmail.com
36,Yuonne,Veum,50,Berry.Rodriguez@gmail.com
37,Harold,Bergnaum,33,Shawanna.Kiehn@gmail.com
38,Franklyn,Hirthe,22,Sherrell.Spinka@gmail.com
39,Les,Klein,43,Edith.Steuber@gmail.com
40,Edmundo,Bogan,27,Colene.Barton@gmail.com
41,Chase,Morissette,9,Benedict.Cartwright@gmail.com
42,Chantay,Leffler,4,Tabitha.Grimes@gmail.com
43,Jan,Reynolds,18,Kia.Grimes@gmail.com
44,Dustin,Hessel,41,Abby.Schmidt@gmail.com
45,Vania,Marks,51,Grazyna.Durgan@gmail.com
46,Kennith,Daugherty,14,Kandis.Ortiz@gmail.com
47,Chase,Lakin,16,Jay.Jacobs@gmail.com
48,Santos,Heller,5,Sammie.Kulas@gmail.com
49,Shayne,Wolf,30,Audie.Bahringer@gmail.com
50,Rusty,Gleichner,11,Jerald.Ferry@gmail.com
51,Jerry,Dickens,67,Ophelia.Daugherty@gmail.com
52,Lynn,Wilderman,29,Rueben.Hartmann@gmail.com
53,Deangelo,Wilderman,17,Donte.Gorczany@gmail.com
54,Robert,Gutkowski,39,Michelina.Powlowski@gmail.com
55,Neely,Bauch,59,Cecil.Graham@gmail.com
56,Toshia,Breitenberg,66,Silas.Cole@gmail.com
57,Rico,Klein,77,Jarrett.Nader@gmail.com
58,Galen,Deckow,76,Ardath.Leffler@gmail.com
59,Alisia,Hilpert,42,Yon.O'Conner@gmail.com
60,Bella,Bergnaum,17,Karl.West@gmail.com
61,Stepanie,Bruen,74,Araceli.Osinski@gmail.com
62,Andree,Balistreri,66,Pandora.Pollich@gmail.com
63,Lowell,McKenzie,11,Lavina.Marquardt@gmail.com
64,Eloy,Russel,2,Shirl.Blick@gmail.com
65,Krystina,Kuphal,36,Dominick.Hane@gmail.com
66,Reed,Jenkins,50,Tiera.Casper@gmail.com
67,Toney,Collier,64,Daniell.Dietrich@gmail.com
68,Lemuel,Lebsack,74,Nicolle.Stracke@gmail.com
69,Charles,Hermiston,44,Zachery.Treutel@gmail.com
70,Julissa,Boehm,22,Darnell.Beatty@gmail.com
71,Jerri,Kuhn,14,Lorene.Fay@gmail.com
72,Odell,Kozey,55,Dorla.Bauch@gmail.com
73,Dione,Dickinson,51,Jarrett.Skiles@gmail.com
74,Johnathon,Thiel,2,Mei.Boyer@gmail.com
75,Gilberto,Schultz,79,Karey.Vandervort@gmail.com
76,Malena,Pagac,58,Signe.Rice@gmail.com
77,Jared,Hickle,51,Eneida.Bechtelar@gmail.com
78,Roland,McKenzie,46,Jamaal.Schamberger@gmail.com
79,Erlinda,Hills,10,Will.O'Keefe@gmail.com
80,Mina,Bins,34,Lekisha.Mraz@gmail.com
81,Lyla,Hoppe,3,Elroy.Kutch@gmail.com
82,Grant,Williamson,13,Terrence.Hamill@gmail.com
83,Darrin,Weissnat,3,Geralyn.Prohaska@gmail.com
84,Angla,Rodriguez,70,Allan.Lehner@gmail.com
85,Lenard,Mante,72,Annabell.Boyle@gmail.com
86,Stevie,Keeling,16,Elza.Steuber@gmail.com
87,Nathan,Becker,54,Gail.Berge@gmail.com
88,Bryan,Brown,54,Reba.Schowalter@gmail.com
89,Milford,Koelpin,22,Leandro.Weissnat@gmail.com
90,Phebe,Gibson,55,Brittaney.Rolfson@gmail.com
91,Peter,Rohan,67,Kenyetta.Jones@gmail.com
92,Rocky,Trantow,27,Shelli.Murray@gmail.com
93,Daisy,Gaylord,65,Tisha.Lesch@gmail.com
94,Carlee,Friesen,56,Phyllis.Bartell@gmail.com
95,Howard,Frami,12,Keven.Watsica@gmail.com
96,Lonny,Leuschke,35,Abraham.Orn@gmail.com
97,Adelle,Funk,7,America.Runte@gmail.com
98,Jackie,Hane,36,Natashia.Parisian@gmail.com
99,Rod,Spinka,79,Wilma.Koch@gmail.com
100,Danica,Stroman,24,Rosamond.Weber@gmail.com
101,Monroe,Corwin,56,Hubert.Fritsch@gmail.com
jaxb.properties
javax.xml.bind.context.factory=com.sun.xml.bind.v2.JAXBContextFactory
spring-batch-context.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!-- Step will need a transaction manager -->
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>
<bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="classpath:employee.csv" />
<property name="linesToSkip" value="1" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="fieldSetMapper">
<!-- Mapper which maps each individual items in a record to properties in POJO -->
<bean class="com.example.mapper.EmployeeFieldSetMapper" />
</property>
<property name="lineTokenizer">
<!-- A tokenizer class to be used when items in input record are separated by specific characters -->
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="delimiter" value=","/>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="xmlItemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
<property name="resource" value="file:xml/employee.xml"/>
<property name="rootTagName" value="EmployeeList"/>
<property name="marshaller">
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.example.model.Employee</value>
</list>
</property>
</bean>
</property>
</bean>
<batch:job id="employeeJob">
<batch:step id="step1">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="flatFileItemReader" writer="xmlItemWriter"
commit-interval="10"/>
</batch:tasklet>
</batch:step>
<batch:listeners>
<batch:listener ref="jobListener"/>
</batch:listeners>
</batch:job>
<bean id="jobListener" class="com.example.listener.EmployeeListener" />
</beans>
App Logs
2024-06-04 17:54:32.500 INFO 13057 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2024-06-04 17:54:32.651 INFO 13057 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=employeeJob]] launched with the following parameters: [{}]
ExamResult Job starts at :2024-06-04T17:54:32.664106
2024-06-04 17:54:32.669 INFO 13057 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
2024-06-04 17:54:32.751 INFO 13057 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 82ms
ExamResult Job stops at :2024-06-04T17:54:32.754281
Total time take in millis :90
ExamResult job completed successfully
2024-06-04 17:54:32.757 INFO 13057 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=employeeJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 93ms
Job Exit Status : COMPLETED
Output
Here is the another way to read the csv file
<bean id="cvsFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="classpath:cvs/input/report.csv" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="id,sales,qty,staffName,date" />
</bean>
</property>
<property name="fieldSetMapper">
<bean class="com.mkyong.ReportFieldSetMapper" />
<!-- if no data type conversion, use BeanWrapperFieldSetMapper to map by name
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="report" />
</bean>
-->
</property>
</bean>
</property>
</bean>
ReportFieldSetMapper.java — when you want to enrich the data.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
import com.mkyong.model.Report;
/**
* Interface that is used to map data obtained from a FieldSet into an object.
*/
public class ReportFieldSetMapper implements FieldSetMapper<Report> {
private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
@Override
public Report mapFieldSet(FieldSet fieldSet) throws BindException {
Report report = new Report();
report.setId(fieldSet.readInt(0));
report.setSales(fieldSet.readBigDecimal(1));
report.setQty(fieldSet.readInt(2));
report.setStaffName(fieldSet.readString(3));
//default format yyyy-MM-dd
//fieldSet.readDate(4);
String date = fieldSet.readString(4);
try {
report.setDate(dateFormat.parse(date));
} catch (ParseException e) {
e.printStackTrace();
}
return report;
}
}