In this example, we’ll make the use of CompositeItemWriter to write to the multiple destinations. In this example, we will write same data to CSV and XML. CompositeItemWriter is the good when you want to write data to multiple places.
pom.xml — with all the dependencies needed for the 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>
Customer.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.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 — An interface used by JdbcTemplate for mapping rows of a ResultSet on a per-row basis. Implementations of this interface perform the actual work of mapping each row to a result object, but don’t need to worry about exception handling. SQLExceptions will be caught and handled by the calling JdbcTemplate.
Typically used either for JdbcTemplate’s query methods or for out parameters of stored procedures. RowMapper objects are typically stateless and thus reusable; they are an ideal choice for implementing row-mapping logic in a single place.
Alternatively, consider subclassing org.springframework.jdbc.object.MappingSqlQuery from the jdbc.object package: Instead of working with separate JdbcTemplate and RowMapper objects, you can build executable query objects (containing row-mapping logic) in that style.
package com.example.mapper;
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();
}
}
MainApp.java
package com.example;
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 CompositeItemWriterApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(CompositeItemWriterApplication.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();
}
}
}
DB.sql
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');
spring-batch.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util-2.5.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="itemWriters" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="csvItemWriter" />
<ref bean="xmlItemWriter" />
</list>
</constructor-arg>
</bean>
<bean id="compositeItemWriter" class="org.springframework.batch.item.support.CompositeItemWriter" >
<property name="delegates" ref="itemWriters" />
</bean>
<batch:job id="employeeJob">
<batch:step id="step1">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="employeeItemReader" writer="compositeItemWriter"
commit-interval="10">
</batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
</beans>
Output — Two records being writen to XML
<?xml version="1.0" encoding="UTF-8"?>
<Customers>
<Customer>
<id>1</id>
<firstName>John</firstName>
<lastName>Doe</lastName>
<birthdate>john.doe@gmail.com</birthdate>
</Customer>
<Customer>
<id>2</id>
<firstName>Jane</firstName>
<lastName>Doe</lastName>
<birthdate>jane.doe@gmail.com</birthdate>
</Customer>
</Customers>
Also same being writen to
1,John,Doe,john.doe@gmail.com
2,Jane,Doe,jane.doe@gmail.com