Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

This is a simple Spring Batch project. This implementation will read an XML file and write an XML file with almost no Java code.

Maven Dependencies

The Maven dependency for Spring Batch is:

<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-core</artifactId>
    <version>${spring.batch.version}</version>
</dependency>

Please note that Spring Batch does use the Spring Framework. Spring Batch 3.0.6 corresponds to Spring 4.0.5. Attempting to use a newer version of Spring will cause runtime errors.

You will also need to include the spring-oxm dependency to pull in the XML to object mapping library.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
    <version>${spring.version}</version>
    <exclusions>
        <exclusion>
            <artifactId>commons-logging</artifactId>
            <groupId>commons-logging</groupId>
        </exclusion>
    </exclusions>
</dependency>

Below is my entire pom.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javaninja</groupId>
    <artifactId>spring-batch-xml</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!--This is the version of Spring that Spring Batch uses-->
        <spring.version>4.0.5.RELEASE</spring.version>
        <spring.batch.version>3.0.6.RELEASE</spring.batch.version>
        <slf4j.version>1.7.13</slf4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-core</artifactId>
            <version>${spring.batch.version}</version>
        </dependency>
        <!-- Spring XML processing-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--
            Logging
        -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <!--
            Testing
        -->
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-test</artifactId>
            <version>${spring.batch.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Spring and Spring Batch Context

Spring Batch has a few components that will be common across all of your jobs.

  • JobRepository – This is a repository that holds jobs.
  • JobLauncher – This is used to launch a job.
  • TransactionManager – This is a Batch TransactionManager, not to be confused with the Transaction Managers that you are used to dealing with.

Spring Batch uses Readers and Writers to read and write data. I am going to read and write XML data. To accomplish this, I will use a StaxEventItemReader and a StaxEventItemWriter. As part of the Stax readers and writers, I defined a Jaxb2Marshaller to handle marshalling and unmarshalling the data.

I chose to put all of these in my src/main/resources/applicationContext.xml.

Below is my entire applicationContext.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath:META-INF/spring/batchContext.xml"/>

    <context:component-scan base-package="com.javaninja.batch"/>

    <bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>

    <!--This repository is only really intended for use in testing and rapid prototyping-->
    <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>

    <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"
          p:jobRepository-ref="jobRepository"/>

    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>com.javaninja.batch.Car</value>
            </list>
        </property>
    </bean>

    <!-- ItemReader which reads data from XML file -->
    <bean id="xmlItemReader" class="org.springframework.batch.item.xml.StaxEventItemReader"
          p:resource="classpath:cars-input.xml"
          p:fragmentRootElementName="car"
          p:unmarshaller-ref="jaxb2Marshaller"/>

    <!-- XML ItemWriter which writes the data in XML format -->
    <bean id="xmlItemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter"
        p:resource="file:xml/cars.xml"
        p:rootTagName="cars"
        p:marshaller-ref="jaxb2Marshaller"/>

</beans>

Spring Batch jobs are defined by fairly simple xml. You define a job, with one or more steps. In my case, I have one step. I defined it to use my reader and my writer and commit data in 1000 chunks. I placed this file in src/main/resources/META-INF/spring/batchContext.xml. Below is my entire batchContext.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">

    <batch:job id="carJob">
        <batch:step id="step1">
            <batch:tasklet>
                <batch:chunk reader="xmlItemReader" writer="xmlItemWriter" commit-interval="1000"/>
            </batch:tasklet>
        </batch:step>
    </batch:job>

</beans>

XML Mapping

I used JAXB to map the XML objects. I chose a fairly simple example for demonstration purposes. I also chose to read and write the same XML, without modification.

The JAXB-annotated class is below.

package com.javaninja.batch;

import javax.xml.bind.annotation.XmlRootElement;

/**
 * @author norris.shelton
 */
@XmlRootElement
public class Car {
    private String make;
    private String model;
    private String color;
    private int doors;

    public String getMake() {
        return make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getDoors() {
        return doors;
    }

    public void setDoors(int doors) {
        this.doors = doors;
    }
}

Here is an example of the XML that I will be reading and writing.

<?xml version="1.0" encoding="UTF-8"?>
<cars>
    <car>
        <color>color1</color>
        <doors>1</doors>
        <make>make1</make>
        <model>model1</model>
    </car>
</cars>

Running a Job

Jobs are ran as a Java application. I defined my Main(). Inside, it instantiated an XML-context from the classpath. From there, I retrieved the JobLauncher and the Job that I defined in the batchContext.xml Then I ran the job.

The source is below.

package com.javaninja.batch;

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.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author norris.shelton
 */
public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        JobLauncher jobLauncher = context.getBean("jobLauncher", JobLauncher.class);
        Job job = context.getBean("carJob", Job.class);

        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();
        }
    }
}

When I ran the program, the output directory (xml) was written alongside the project directory, not inside.

Summary

In this blog, I showed you how to create a simple batch job that reads from an XML file and writes to an XML file. This was accomplished with very little business logic.

The entire project used to write this blog is located on GitHub sheltonn / spring-batch-xml

February 17th, 2016

Posted In: Java, java ninja, Javaninja, Spring Batch

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

WP to LinkedIn Auto Publish Powered By : XYZScripts.com