Spring Batch (XML way) — ClassifierCompositeItemWriter

Prateek
3 min readJun 4, 2024

--

In this example, we’ll make the use of ClassifierCompositeItemWriter to write data to two different locations.

ClassifierCompositeItemWriter — Calls one of a collection of ItemWriters for each item, based on a router pattern implemented through the provided Classifier. The implementation is thread-safe if all delegates are thread-safe.

In this example, we’re reading data from MySQL database and writing it to CSV file as well as XML file. If the customerId is even write it to CSV if its odd then write it to XML file.

pom.xml — The complete list of dependencies used in this project.

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</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>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.7</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>

The project structure

Customer.java

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.XmlRootElement;

@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
@XmlRootElement(name = "Customer")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private Long id;
private String firstName;
private String lastName;
private String birthdate;
}

CustomerRowMapper.java

import com.example.model.Customer;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class CustomerRowMapper implements RowMapper<Customer> {

@Override
public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {
return Customer.builder().id(rs.getLong("id"))
.firstName(rs.getString("firstName"))
.lastName(rs.getString("lastName"))
.birthdate(rs.getString("birthdate")).build();
}
}

CustomerClassifier.java

import com.example.model.Customer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.classify.Classifier;

public class CustomerClassifier implements Classifier<Customer, ItemWriter<? super Customer>> {

private static final long serialVersionUID = 1L;

private final ItemWriter<Customer> evenItemWriter;
private final ItemWriter<Customer> oddItemWriter;

public CustomerClassifier(ItemWriter<Customer> evenItemWriter, ItemWriter<Customer> oddItemWriter) {
this.evenItemWriter = evenItemWriter;
this.oddItemWriter = oddItemWriter;
}

@Override
public ItemWriter<? super Customer> classify(Customer customer) {
return customer.getId() % 2 == 0 ? evenItemWriter : oddItemWriter;
}
}

MainApp.java

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;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class XmlSpringBatchCompositeItemWriterApplication implements CommandLineRunner {

public static void main(String[] args) {
SpringApplication.run(XmlSpringBatchCompositeItemWriterApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-batch.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();
}
}
}

MySQL Customer table.

CREATE TABLE `test`.`customer` (
`id` INT NOT NULL,
`firstName` VARCHAR(45) NULL,
`lastName` VARCHAR(45) NULL,
`birthDate` DATETIME NULL,
PRIMARY KEY (`id`));

INSERT INTO test.customer (id, firstName, lastName, birthdate) VALUES(1, 'John', 'Doe', 'john.doe@gmail.com');
INSERT INTO test.customer (id, firstName, lastName, birthdate) VALUES(2, 'Jane', 'Doe', 'jane.doe@gmail.com');

xml file

<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">

<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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="Password"/>
</bean>


<bean id="employeeItemReader" class="org.springframework.batch.item.database.JdbcCursorItemReader">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="SELECT * FROM test.customer"/>
<property name="rowMapper">
<bean class="com.example.mapper.CustomerRowMapper"/>
</property>
</bean>



<!-- XML Writer -->
<bean id="xmlItemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
<property name="resource" value="file:xml/customer.xml"/>
<property name="rootTagName" value="Customers"/>
<property name="marshaller">
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.example.model.Customer</value>
</list>
</property>
</bean>
</property>
</bean>

<!-- CSV Writer -->
<bean id="csvItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:cvs/customer.csv"/>
<property name="shouldDeleteIfExists" value="true"/>

<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value=","/>
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names" value="id, firstName, lastName, birthdate"/>
</bean>
</property>
</bean>
</property>
</bean>

<bean id="customerClassifier" class="com.example.classifier.CustomerClassifier" >
<constructor-arg index="0" ref="xmlItemWriter" />
<constructor-arg index="1" ref="csvItemWriter" />
</bean>

<bean id="classifierCompositeItemWriter" class="org.springframework.batch.item.support.ClassifierCompositeItemWriter" >
<property name="classifier" ref="customerClassifier" />
</bean>

<batch:job id="employeeJob">
<batch:step id="step1">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="employeeItemReader" writer="classifierCompositeItemWriter"
commit-interval="10">
<batch:streams>
<batch:stream ref="csvItemWriter" />
<batch:stream ref="xmlItemWriter" />
</batch:streams>
</batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
</beans>

Result

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
<Customer>
<id>2</id>
<firstName>Jane</firstName>
<lastName>Doe</lastName>
<birthdate>jane.doe@gmail.com</birthdate>
</Customer>
</Customers>

csv

1,John,Doe,john.doe@gmail.com

Referance Link:

--

--

Prateek
Prateek

Written by Prateek

Java Developer and enthusiast

No responses yet