mar

09

Posted by : admin | On : 9 mars 2010


 

In this article, we will walk through some of the capabilities provided by Spring batch Framework

 

Why use Spring Batch? Personally, when I sit down to work with batch, I get frustrated with the amount of code that you need to write to do simple things I/O, manage batches error cases  …

 

Spring batch let’s us use all capability of spring and provide powerful tool with few lines XML configuration.

 

Source :

 

You can checkout the source in Eclipse IDE using SVN here is the address of the source :

 

 

http://code.google.com/p/artaud/source/browse/#svn/springbatch

 

https://github.com/aartaud300/artaud

https://github.com/DoctusHartwald/artaud

In this article we’ll walk through these case :

 

-Case 1 : Hello World
-Case 2 : Import  CSV => Database
Case 3 : Export Database => flat file
-Case 4 : Export Database=> XML
-Case 5 : Couple Spring Batch and  Velocity template.
-Case 6 : export our database to a Jasper Report in PDF.

 

Technologies used :

 

I assume the reader to have good knowledge in basic Spring and Spring Annotations.

 

Spring

  1. Spring Annotation
  2. SpringBatch
  3. Spring OXM
  4. Spring Test

 

Related framework :

  1. Apache  Velocity
  2. Xstream /Stax  (for xml)
  3. Jasper

 

Database : MySQL

 

ENVIRONNEMENT

 

On windows :

 

JDK 5 or more
Apache Maven
MySQL or an other Database provider
Internet Connection

 

Eclipse

 

On Linux :

 

JDK 5 or more
Apache Maven
MySQL or an other Database provider
sudo apt-get  install apache2 mysql-server php5 php5-mysql phpmyadmin

 

Eclipse

 

APACHE MAVEN
In you ~/.bashrc add the following lines .

 

 

 

export JAVA_HOME=/usr/lib/jvm/java-6-sun/jre
 export PATH=${PATH}:$JAVA_HOME/bin
 export M2_HOME=/usr/local/apache-maven/apache-maven-2.2.1
 export M2=$M2_HOME/bin
 export MAVEN_OPTS="-Xms512m -Xmx1024m"
 export PATH=${PATH}:$M2
 export ROO_HOME=/home/<USER>/dev/spring-roo-1.0.2.RELEASE
 export ROO_OPTS="-Droo.bright=true"

 

RUN THE PROJET

SEE job.sh in in the folder documentation/ download the code source here.

Configuration of Maven2

First let’s see the pom.xml

 

 

<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/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>org.batch</groupId>
 <artifactId>springbatch</artifactId>
 <name>SpringbatchDemo</name>
 <version>1.0</version>
 <packaging>jar</packaging>
 <description>Springbatch Sample Projet </description>
 <properties>
 <spring.version>2.5.6</spring.version>
 <!--<spring-batch.version>1.0.1.RELEASE</spring-batch.version>-->
 <spring-batch.version>2.1.0.RELEASE</spring-batch.version>
 <mysql-version>5.1.6</mysql-version>
 </properties>
 <dependencies>
 <!-- MySQL -->
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>${mysql-version}</version>
 </dependency>
 <!-- SPRING -->
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jdbc</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.batch</groupId>
 <artifactId>spring-batch-core</artifactId>
 <version>${spring-batch.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-core</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-beans</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aspects</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jms</artifactId>
 <version>${spring.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.ws</groupId>
 <artifactId>spring-oxm</artifactId>
 <version>1.5.5</version>
 <optional>true</optional>
 </dependency>
 <dependency>
 <groupId>org.springframework.osgi</groupId>
 <artifactId>spring-osgi-core</artifactId>
 <version>1.1.2</version>
 <optional>true</optional>
 </dependency>
 <!-- Log  -->
 <dependency>
 <groupId>commons-logging</groupId>
 <artifactId>commons-logging</artifactId>
 <version>1.1.1</version>
 </dependency>
 <dependency>
 <groupId>log4j</groupId>
 <artifactId>log4j</artifactId>
 <version>1.2.14</version>
 <optional>true</optional>
 </dependency>
 <dependency>
 <groupId>log4j</groupId>
 <artifactId>log4j</artifactId>
 <version>1.2.13</version>
 </dependency>
 <!-- Junit -->
 <dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.4</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>org.springframework.test</artifactId>
 <version>2.5.6</version>
 <scope>test</scope>
 </dependency>
 <!-- Jasper report -->
 <dependency>
 <groupId>jasperreports</groupId>
 <artifactId>jasperreports</artifactId>
 <version>3.1.2</version>
 </dependency>
 <!-- XML STAX -->
 <dependency>
 <groupId>stax</groupId>
 <artifactId>stax</artifactId>
 <version>1.2.0</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.batch</groupId>
 <artifactId>spring-batch-test</artifactId>
 <version>${spring-batch.version}</version>
 </dependency>
 <dependency>
 <groupId>commons-lang</groupId>
 <artifactId>commons-lang</artifactId>
 <version>2.4</version>
 </dependency>
 <!-- Velocity -->
 <dependency>
 <groupId>org.apache.velocity</groupId>
 <artifactId>velocity</artifactId>
 <version>1.6.3</version>
 </dependency>
 <dependency>
 <groupId>org.apache.velocity</groupId>
 <artifactId>velocity-tools</artifactId>
 <version>1.3</version>
 </dependency>
 </dependencies>
 <distributionManagement>
 <repository>
 <id>spring-release</id>
 <name>Spring Release Repository</name>
 <url>s3://maven.springframework.org/release</url>
 </repository>
 <snapshotRepository>
 <id>spring-snapshot</id>
 <name>Spring Snapshot Repository</name>
 <url>s3://maven.springframework.org/snapshot</url>
 </snapshotRepository>
 </distributionManagement>
 <repositories>
 <repository>
 <id>objectstyle</id>
 <name>ObjectStyle.org Repository</name>
 <url>http://objectstyle.org/maven2</url>
 <snapshots>
 <enabled>false</enabled>
 </snapshots>
 </repository>
 <repository>
 <id>com.springsource.repository.bundles.release</id>
 <name>EBR Spring Release Repository</name>
 <url>http:// repository.springsource.com/maven/bundles/release</url>
 </repository>
 <repository>
 <id>com.springsource.repository.bundles.external</id>
 <name>EBR External Release Repository</name>
 <url>http:// repository.springsource.com/maven/bundles/external</url>
 </repository>
 <repository>
 <id>codehaus</id>
 <name>Maven Codehaus repository</name>
 <url>http://repository.codehaus.org/</url>
 </repository>
 </repositories>
 <pluginRepositories>
 <pluginRepository>
 <id>com.springsource.repository.bundles.milestone</id>
 <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Milestones</name>
 <url>http://repository.springsource.com/maven/bundles/milestone</url>
 <snapshots>
 <enabled>false</enabled>
 </snapshots>
 </pluginRepository>
 <pluginRepository>
 <id>com.springsource.repository.bundles.snapshot</id>
 <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Snapshots</name>
 <url>http://repository.springsource.com/maven/bundles/snapshot</url>
 </pluginRepository>
 </pluginRepositories>
 <ciManagement>
 <system>Hudson</system>
 </ciManagement>

 <build>
 <plugins>
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <configuration>
 <source>1.5</source>
 <target>1.5</target>
 </configuration>
 </plugin>
 <plugin>
 <artifactId>maven-surefire-plugin</artifactId>
 <version>2.4.3</version>
 <configuration>
 </configuration>
 </plugin>
 <!--
 Decomment this blog if you want to run it with
 -Dexec.mainClass="org.springframework.batch.core.launch.support.CommandLineJobRunner"
 -Dexec.args="com/batch/simpletask/simpletaskletcontext.xml
 simpleJob"
 -->
 <plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>exec-maven-plugin</artifactId>
 <executions>
 <execution>
 <id>glue-processing</id>
 <phase>install</phase>
 <goals>
 <goal>exec</goal>
 </goals>
 </execution>
 </executions>
 <configuration>
 <debug>true</debug>
 <executable>java</executable>
 <mainClass>app_example2.Example2</mainClass>
 <!--
 <mainClass>org.springframework.batch.core.launch.support.CommandLineJobRunner</mainClass>
 <arguments>
 <argument>com/batch/simpletask/simpletaskletcontext.xml</argument>
 <argument>simpleJob</argument> </arguments>
 -->
 </configuration>
 </plugin>
 </plugins>
 </build>
 </project>

 

 

We’ll use Spring annotation couple with standard Spring to realize our sample project

create a package src/main/java/com.batch

these 2  class AppJobExecutionListener and ItemFailureLoggerListener let us log the job execution.

 

 
 package com.batch;
 import org.apache.log4j.Logger;
 import org.springframework.batch.core.BatchStatus;
 import org.springframework.batch.core.JobExecution;
 import org.springframework.batch.core.JobExecutionListener;
 import org.springframework.stereotype.Component;
 @Component("appJobExecutionListener")
 public class AppJobExecutionListener implements JobExecutionListener {
 private final static Logger logger = Logger
 .getLogger(AppJobExecutionListener.class);
 public void afterJob(JobExecution jobExecution) {
 if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
 logger.info("Job completed: " + jobExecution.getJobId());
 } else if (jobExecution.getStatus() == BatchStatus.FAILED) {
 logger.info("Job failed: " + jobExecution.getJobId());
 }
 }
 public void beforeJob(JobExecution jobExecution) {
 if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
 logger.info("Job completed: " + jobExecution.getJobId());
 } else if (jobExecution.getStatus() == BatchStatus.FAILED) {
 logger.info("Job failed: " + jobExecution.getJobId());
 }
 }
 }
 package com.batch;
 import org.apache.log4j.Logger;
 import org.springframework.batch.core.listener.ItemListenerSupport;
 import org.springframework.stereotype.Component;
 @Component("itemFailureLoggerListener")
 public class ItemFailureLoggerListener extends ItemListenerSupport {
 private final static Logger logger = Logger
 .getLogger(ItemFailureLoggerListener.class);
 public void onReadError(Exception ex) {
 logger.error("Encountered error on read", ex);
 }
 public void onWriteError(Exception ex, Object item) {
 logger.error("Encountered error on write", ex);
 }
 }

 

 

First example in Spring Batch

HelloWorld in Spring Batch

Create a package name com.batch.simpletask under src/main/java

 

 

package com.batch.simpletask;
 import org.springframework.batch.core.StepContribution;
 import org.springframework.batch.core.scope.context.ChunkContext;
 import org.springframework.batch.core.step.tasklet.Tasklet;
 import org.springframework.batch.repeat.RepeatStatus;
 public class HelloTask implements Tasklet {
 private String taskStartMessage;
 public void setTaskStartMessage(String taskStartMessage) {
 this.taskStartMessage = taskStartMessage;
 }
 public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
 throws Exception {
 System.out.println(taskStartMessage);
 return RepeatStatus.FINISHED;
 }
 }

 

 

package com.batch.simpletask;
 import java.util.Calendar;
 import org.springframework.batch.core.StepContribution;
 import org.springframework.batch.core.scope.context.ChunkContext;
 import org.springframework.batch.core.step.tasklet.Tasklet;
 import org.springframework.batch.repeat.RepeatStatus;
 public class TimeTask implements Tasklet {
 public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
 throws Exception {
 System.out.println(Calendar.getInstance().getTime());
 return RepeatStatus.FINISHED;
 }
 }

 

 

Spring Configuration

 

 
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:batch="http://www.springframework.org/schema/batch"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
 http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
 <!-- 1) USE ANNOTATIONS TO IDENTIFY AND WIRE SPRING BEANS. -->
 <!-- We indicate to Spring that we scan @Component annotation based on package com.batch -->
 <context:component-scan base-package="com.batch" />
 <bean id="transactionManager"
 class="org.springframework.batch.support.transaction.ResourcelessTransactionManager">
 </bean>
 <!-- 3) JOB REPOSITORY - WE USE IN-MEMORY REPOSITORY FOR OUR EXAMPLE -->
 <bean id="jobRepository"
 class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
 <property name="transactionManager" ref="transactionManager" />
 </bean>
 <!-- 4) LAUNCH JOBS FROM A REPOSITORY -->
 <bean id="jobLauncher"
 class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
 <property name="jobRepository" ref="jobRepository" />
 </bean>
 <!-- 5) Beans representing the 2 job steps. -->
 <!-- Step1 - print hello world -->
 <bean id="helloTask">
 <property name="taskStartMessage" value="Hello World - the time is now " />
 </bean>
 <!-- Step2 - print current time -->
 <bean id="timeTask" />
 <!-- 6) FINALLY OUR JOB DEFINITION. THIS IS A 2 STEP JOB -->
 <batch:job id="simpleJob">
 <batch:listeners>
 <batch:listener ref="appJobExecutionListener" />
 </batch:listeners>
 <batch:step id="step1" next="step2">
 <batch:tasklet ref="helloTask" />
 </batch:step>
 <batch:step id="step2">
 <batch:tasklet ref="timeTask" />
 </batch:step>
 </batch:job>
 </beans>

 

 

Test our Case 1 :

 

 package com.batch.simpletask;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.batch.core.Job;
 import org.springframework.batch.core.JobParameters;
 import org.springframework.batch.core.launch.JobLauncher;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.util.StopWatch;
 @ContextConfiguration(locations = "classpath*:com/batch/simpletask/simpletaskletcontext.xml")
 @RunWith(SpringJUnit4ClassRunner.class)
 public class SimpleTaskletTestCase extends
 AbstractDependencyInjectionSpringContextTests {
 private final static Logger logger = Logger
 .getLogger(SimpleTaskletTestCase.class);
 @Autowired
 private JobLauncher launcher;
 @Autowired
 private Job job;
 private JobParameters jobParameters = new JobParameters();
 @Before
 public void setup() {
 //PropertyConfigurator.configure("C:/Users/admin/workspace-test/springbatch2/src/com/batch/log4j.properties");
 }
 @Test
 public void testLaunchJob() throws Exception {
 StopWatch sw = new StopWatch();
 sw.start();
 launcher.run(job, jobParameters);
 sw.stop();
 logger.info(">>> TIME ELAPSED:" + sw.prettyPrint());
 }
 @Autowired
 public void setLauncher(JobLauncher bootstrap) {
 this.launcher = bootstrap;
 }
 @Autowired
 public void setJob(Job job) {
 this.job = job;
 }
 }

 

 

Install a LAMP server

  • on Linux :

 

sudo apt-get isntall apache2 mysql-server php5 php5-mysql phpmyadmin

 

Check that the process are running

 

 

sudo /etc/init.d/apache2 start
 sudo /etc/init.d/mysql restart

 

  • on Window

Download EasyPHP or Xampp and launch the application

Connect to MySQL

Connect to MySql whether by prompt or via Phpmyadmin http://localhost/phpmyadmin

Create a new user

  • via phpmyadmin

Enter login : test / password testand tick the right rules on the database as the following screenshot

Via prompt :

 

sudo mysql -u root -p

 

CREATE USER 'test'@'%' IDENTIFIED  BY  '***';
 GRANT  SELECT ,
 INSERT ,
 UPDATE ,
 DELETE ,
 CREATE ,
 DROP ,
 FILE ,
 INDEX ,
 ALTER ,
 CREATE  TEMPORARY  TABLES ,
 CREATE  VIEW ,
 EVENT,
 TRIGGER,
 SHOW  VIEW ,
 CREATE ROUTINE,
 ALTER ROUTINE,
 EXECUTE  ON  *  .  *  TO  'test'@'%' IDENTIFIED  BY  '***' WITH  MAX_QUERIES_PER_HOUR 0  MAX_CONNECTIONS_PER_HOUR 0  MAX_UPDATES_PER_HOUR 0  MAX_USER_CONNECTIONS 0 ;

 

 

Create a Database

  • create a new database name seamdb
 CREATE  DATABASE  `seamdb` ;

Create a new Table

[sql]

create table ledger (

ID INT NOT NULL AUTO_INCREMENT,

rcv_dt date,

mbr_nm VARCHAR(100) not null,

chk_nbr VARCHAR(10) not null,

chk_dt date,

pymt_typ VARCHAR(50) not null,

dpst_amt double,

pymt_amt double,

comments VARCHAR(100),

PRIMARY KEY (ID)

);

[/sql]

Example 2: CSV to Database

create a new package com.batch.todb in /src/main/java

Create a Model Bean call Ledger

 

package com.batch.todb;

import java.util.Date;

public class Ledger {

private int id;

private Date receiptDate;

private String memberName;

private String checkNumber;

private Date checkDate;

private String paymentType;

private double depositAmount;

private double paymentAmount;

private String comments;

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public Date getReceiptDate() {

return receiptDate;

}

public void setReceiptDate(Date receiptDate) {

this.receiptDate = receiptDate;

}

public String getMemberName() {

return memberName;

}

public void setMemberName(String memberName) {

this.memberName = memberName;

}

public String getCheckNumber() {

return checkNumber;

}

public void setCheckNumber(String checkNumber) {

this.checkNumber = checkNumber;

}

public Date getCheckDate() {

return checkDate;

}

public void setCheckDate(Date checkDate) {

this.checkDate = checkDate;

}

public String getPaymentType() {

return paymentType;

}

public void setPaymentType(String paymentType) {

this.paymentType = paymentType;

}

public double getDepositAmount() {

return depositAmount;

}

public void setDepositAmount(double depositAmount) {

this.depositAmount = depositAmount;

}

public double getPaymentAmount() {

return paymentAmount;

}

public void setPaymentAmount(double paymentAmount) {

this.paymentAmount = paymentAmount;

}

public String getComments() {

return comments;

}

public void setComments(String comments) {

this.comments = comments;

}

}

package com.batch.todb;

public interface LedgerDAO {

public void save(final Ledger note);

}

package com.batch.todb;

import java.sql.PreparedStatement;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.core.PreparedStatementSetter;

import org.springframework.stereotype.Component;

import org.springframework.transaction.annotation.Propagation;

import org.springframework.transaction.annotation.Transactional;

@Component

public class LedgerDAOImpl extends JdbcTemplate implements LedgerDAO {

@Autowired

public void setDataSource(DataSource dataSource) {

super.setDataSource(dataSource);

}

@Transactional(propagation = Propagation.REQUIRED)

public void save(final Ledger item) {

super

.update(

« insert into ledger (rcv_dt, mbr_nm, chk_nbr, chk_dt, pymt_typ, dpst_amt, pymt_amt, comments) values(?,?,?,?,?,?,?,?) »,

new PreparedStatementSetter() {

public void setValues(PreparedStatement stmt)

throws SQLException {

stmt.setDate(1, new java.sql.Date(item

.getReceiptDate().getTime()));

stmt.setString(2, item.getMemberName());

stmt.setString(3, item.getCheckNumber());

stmt.setDate(4, new java.sql.Date(item

.getCheckDate().getTime()));

stmt.setString(5, item.getPaymentType());

stmt.setDouble(6, item.getDepositAmount());

stmt.setDouble(7, item.getPaymentAmount());

stmt.setString(8, item.getComments());

}

});

}

}

package com.batch.todb;

import java.text.DecimalFormat;

import java.text.ParseException;

import org.springframework.batch.item.file.mapping.FieldSetMapper;

import org.springframework.batch.item.file.transform.FieldSet;

import org.springframework.stereotype.Component;

import org.springframework.stereotype.Service;

@Component(« ledgerMapper »)

public class LedgerMapper implements FieldSetMapper {

private final static String DATE_PATTERN = « mm/DD/yy »;

private final static String DOLLAR_PATTERN = « $###,###.### »;

public Object mapFieldSet(FieldSet fs) {

Ledger item = new Ledger();

int idx = 0;

item.setReceiptDate(fs.readDate(idx++, DATE_PATTERN));

item.setMemberName(fs.readString(idx++));

item.setCheckNumber(fs.readString(idx++));

item.setCheckDate(fs.readDate(idx++, DATE_PATTERN));

item.setPaymentType(fs.readString(idx++));

// deposit amount

try {

DecimalFormat fmttr = new DecimalFormat(DOLLAR_PATTERN);

Number number = fmttr.parse(fs.readString(idx++));

item.setDepositAmount(number.doubleValue());

} catch (ParseException e) {

item.setDepositAmount(0);

}

// payment amount

try {

DecimalFormat fmttr = new DecimalFormat(DOLLAR_PATTERN);

Number number = fmttr.parse(fs.readString(idx++));

item.setPaymentAmount(number.doubleValue());

} catch (ParseException e) {

item.setPaymentAmount(0);

}

//

return item;

}

}

package com.batch.todb;

import java.util.Iterator;

import java.util.List;

import org.springframework.batch.item.ItemWriter;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Component(« itemWriter »)

public class LedgerWriter implements ItemWriter {

@Autowired

private LedgerDAO itemDAO;

public void write(List items) throws Exception {

for (Iterator<Ledger> iterator = items.iterator(); iterator.hasNext();) {

Ledger item = iterator.next();

itemDAO.save(item);

}

}

}

Launch Junit Test

In src/test/java create this class of Test

We’ll launch our test using Spring Test framework by using these annotations :

[java]

<pre>@ContextConfiguration(locations = « classpath:com/batch/todb/contextToDB.xml »)

@RunWith(SpringJUnit4ClassRunner.class)

@TransactionConfiguration(transactionManager = « transactionManager », defaultRollback = false)</pre>

[/java]

[java]

package com.batch.todb;

import org.apache.log4j.Logger;

import org.apache.log4j.PropertyConfigurator;

import org.junit.Before;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.batch.core.Job;

import org.springframework.batch.core.JobParameters;

import org.springframework.batch.core.launch.JobLauncher;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.test.context.transaction.TransactionConfiguration;

import org.springframework.util.StopWatch;

@ContextConfiguration(locations = « classpath:com/batch/todb/contextToDB.xml »)

@RunWith(SpringJUnit4ClassRunner.class)

@TransactionConfiguration(transactionManager = « transactionManager », defaultRollback = false)

public class ToDBBatchTestCase extends

AbstractTransactionalJUnit4SpringContextTests {

private final static Logger logger = Logger.getLogger(ToDBBatchTestCase.class);

@Autowired

private JobLauncher launcher;

@Autowired

private Job job;

private JobParameters jobParameters = new JobParameters();

@Before

public void setup() {

//PropertyConfigurator.configure(« C:/Users/admin/workspace-test/springbatch2/src/com/batch/log4j.properties »);

}

@Test

public void testLaunchJob() throws Exception {

StopWatch sw = new StopWatch();

sw.start();

launcher.run(job, jobParameters);

sw.stop();

logger.info(« >>> TIME ELAPSED: » + sw.shortSummary());

}

@Autowired

public void setLauncher(JobLauncher bootstrap) {

this.launcher = bootstrap;

}

@Autowired

public void setJob(Job job) {

this.job = job;

}

}

[/java]

Case 3 : Export Database => flat file

Case 4 : Export Database=> XML

Case 5 : Couple Spring Batch and  Velocity template.

the source are locate into com/batch/velocity

The apache Velocity framework is issue from Jakarta project and is a powerful tool for generating SQL code,Java class C++ , whatever ..

Aim :

In this study case I propose to see how with Spring batch we can couple the two framework together.

We could also use different implementation of  ItemProcessor<T,V> between the ItemReader<T> and ItemWriter<T>  provided by Spring batch , but in this study case it’s not the purpose.

The purpose it to respond fast to our need , generate a bunch of file according to a pattern file .

For instance , you want to generate « a large and complex bouchon class » or « the same SQL code a number of time «   .

The problem is that in a project time is precious so you don’t want to waste it by doing long and repetitif task . I think you grab I want to say ;)

in this sample I start from CSV file and want to replace two variable $name and $ project using this file pattern (the .vm file in Apache Velocity)

Raw input that I have

-Input :

Apache Velocity,Jakarta

Spring batch,Spring source

Spring Roo,Spring Source

-Output expected by the client :

Hello from $name in the $project project.

Spring configuration

What we need is to configure a of a velocityEngine and ItemReader to read our CSV .

Configuration of Velocity Engine

[xml]

<bean id= »velocityEngine »

>

<property name= »velocityProperties »>

<value>

resource.loader=class

class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader

</value>

</property>

</bean>

[/xml]

Configuration of the ItemReader

[xml]

<bean id= »itemReader »>

<property name= »resource » value= »classpath:com/batch/velocity/project.txt » />

<!– property name= »linesToSkip » value= »1″ /–>

<property name= »lineMapper »>

<bean>

<property name= »lineTokenizer »>

<bean

class= »org.springframework.batch.item.file.transform.DelimitedLineTokenizer »>

<property name= »names » value= »name,project » />

</bean>

</property>

<property name= »fieldSetMapper » ref= »projectMapper » />

</bean>

</property>

</bean>

[/xml]

Full spring configuration

[xml]

<?xml version= »1.0″ encoding= »UTF-8″?>

<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:aop= »http://www.springframework.org/schema/aop » xmlns:tx= »http://www.springframework.org/schema/tx »

xmlns:batch= »http://www.springframework.org/schema/batch »

xmlns:context= »http://www.springframework.org/schema/context »

xmlns:jms= »http://www.springframework.org/schema/jms » xmlns:amq= »http://activemq.apache.org/schema/core »

xmlns:util= »http://www.springframework.org/schema/util »

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-2.1.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/jms

http://www.springframework.org/schema/jms/spring-jms.xsd

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd

« >

<!– 1) USE ANNOTATIONS TO CONFIGURE SPRING BEANS –>

<context:component-scan base-package= »com.batch.velocity » />

<!– Author Artaud Antoine  –>

<bean id= »velocityEngine »

class= »org.springframework.ui.velocity.VelocityEngineFactoryBean »>

<property name= »velocityProperties »>

<value>

resource.loader=class

class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader

</value>

</property>

</bean>

<bean id= »itemReader »>

<property name= »resource » value= »classpath:com/batch/velocity/project.txt » />

<!– property name= »linesToSkip » value= »1″ /–>

<property name= »lineMapper »>

<bean>

<property name= »lineTokenizer »>

<bean

class= »org.springframework.batch.item.file.transform.DelimitedLineTokenizer »>

<property name= »names » value= »name,project » />

</bean>

</property>

<property name= »fieldSetMapper » ref= »projectMapper » />

</bean>

</property>

</bean>

</beans>

[/xml]

Case 6 : export our database to a Jasper Report in PDF.

Case 7 : Couple Spring Batch and  Velocity template + treatement

-Aim : use the 2 framework together and add some basic treatement on data you receive from a CSV file for instance

The configuration XML is the same as previously.

- the only difference reside in the vm template (VTL) that we write in Apache Velocity template .

[xml]

<?xml version= »1.0″ encoding= »UTF-8″?>

<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:aop= »http://www.springframework.org/schema/aop » xmlns:tx= »http://www.springframework.org/schema/tx »

xmlns:batch= »http://www.springframework.org/schema/batch »

xmlns:context= »http://www.springframework.org/schema/context »

xmlns:jms= »http://www.springframework.org/schema/jms » xmlns:amq= »http://activemq.apache.org/schema/core »

xmlns:util= »http://www.springframework.org/schema/util »

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-2.1.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/jms

http://www.springframework.org/schema/jms/spring-jms.xsd

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd

« >

<!– 1) USE ANNOTATIONS TO CONFIGURE SPRING BEANS –>

<context:component-scan base-package= »com.batch.velocity.genClass » />

<!– Author Artaud Antoine  –>

<bean id= »velocityEngine »

class= »org.springframework.ui.velocity.VelocityEngineFactoryBean »>

<property name= »velocityProperties »>

<value>

resource.loader=cla

ss

class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader

</value>

</property>

</bean>

<bean id= »itemReader »>

<property name= »resource » value= »classpath:com/batch/velocity/genClass/dataTest.csv » />

<!– property name= »linesToSkip » value= »1″ /–>

<property name= »lineMapper »>

<bean>

<property name= »lineTokenizer »>

<bean>

<!–<property name= »delimiter » value= »MonDel »/> –>

<property name= »names » value= »champ1,data1Type,documentation1,champ2,data2Type,documentation2″ />

</bean>

</property>

<property name= »fieldSetMapper » ref= »genMapperFactoryMapper » />

</bean>

</property>

</bean>

</beans>

[/xml]

Apache Velocity template

what interest us is the bold part of the bellow code .It indicate to apache Velocity that you call this custom metod treatement on the data you furnish on the « pipe ».

SEE the it /src/test/ressources/com/batch/velocity/genClass/example2.vm

[bash]

/**

*

*/

public class $name {

#foreach( $element in $mylist )

private  $element.Data1Type   $element.champ1;

/**

*

* Map : $element.champ1,$element.data1Type,$element.documentation1

* To : $element.champ2,$element.data2Type,$element.documentation2

* $element.documentation1

* @return the $element.champ1

*

*/

public $element.data1Type <strong>get${utility.getInputU($element.champ1)}</strong>() {

return  $element.champ1;

}

/**

* Map : $element.champ1,$element.data1Type,$element.documentation1

* To : $element.champ2,$element.data2Type,$element.documentation2

* $element.documentation1

* @param $element.champ1 the $element.champ1 to set

* #set( $setter = « set » )

*/

public void  <strong>set${utility.getInputU($element.champ1)}</strong>($element.data1Type $element.champ1) {

this.$element.champ1 = $element.champ1;

}

#end

}

[/bash]

Bellow the Java code that is being called by Apache Velocity

[java]

public class GeneratorUtility {

/**

* set$utility.firstToUpperCase($att.Name)

* @param input the input to set

*/

public String  <strong>getInput</strong>U(String pInput) {

pInput.split(« _ »);

return StringUtils.capitalizeFirstLetter(pInput);

}

}

[/java]

Junit Test

[java]

@ContextConfiguration(locations = « classpath:com/batch/velocity/genClass/contextVelocity.xml »)

@RunWith(SpringJUnit4ClassRunner.class)

public class VelocityCodeTemplateGenClassGenerator {

@Autowired

private VelocityEngine velocityEngine;

@Autowired

private FlatFileItemReader vFlatFileItemReader;

@Test

public void testFlatReaderItemToVelocityProcessor(){

System.out.println(« [INFO] ———————————————————————————- »);

System.out.println(« [INFO] Running test : testFlatReaderItemToVelocityProcessor / « + getClass().getName());

System.out.println(« [INFO] ———————————————————————————- »);

vFlatFileItemReader.open(new ExecutionContext());

boolean hasNext = true ;

GenMapperFactory  vGenMapperFactory = null;

GeneratorUtility vUtility = new GeneratorUtility();

List<GenMapperFactory> vGenMapperFactoryList = new ArrayList<GenMapperFactory>();

//vMyClasseGen.setGenMapperFactory(new ArrayList<GenMapperFactory>());

while (hasNext) {

try {

vGenMapperFactory = (GenMapperFactory) vFlatFileItemReader.read();

vGenMapperFactoryList.add(vGenMapperFactory);

} catch (UnexpectedInputException e) {

e.printStackTrace();

} catch (ParseException e) {

e.printStackTrace();

} catch (Exception e) {

e.printStackTrace();

}

if (vGenMapperFactory == null) {

hasNext = false;

}

else {

Map model = new HashMap();

model.put(« utility », vUtility);

model.put(« mylist », vGenMapperFactoryList);

model.put(« name », « ClassDeTest »);

String text = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, « com/batch/velocity/genClass/example2.vm », model);

System.out.println( » « +text);

System.out.println(vGenMapperFactory.toString());

}

}

}

}

[/java]