Sunday 6 September 2015

How to execute a Batch Job Manually in Spring ?




What is Spring Batch ?

Spring Batch is an open source framework for batch processing. It is a lightweight, comprehensive solution designed to enable the development of robust batch applications, which are often found in modern enterprise systems. Spring Batch builds upon the POJO-based development approach of the Spring Framework. Lets take a look

So now lets set up out pom.xml file so basically we need 4 tyepe of dependencies to execute our spring job.
  • spring bom(bill of materials) 
  • spring web-mvc
  • spring batch-core
  • spring batch-infrastructure

What is Spring BOM ? 

If you have worked on maven in your projects for dependency management, then you must have faced one problem at least once or may be more than that. And the problem is version mismatch. It generally happens when you got some dependencies which bring it’s related dependencies together with certain version. And if you have included those dependencies with different version numbers already, they can face undesired results in compile time as well as runtime also.

Ideally to avoid above issue you need to explicitly exclude the related dependency, but it is quite possible that you can forget to do so. 

will add all those dependencies in 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>com.spring.batch</groupId>
 <artifactId>SpringBatchManully</artifactId>
 <packaging>war</packaging>
 <version>0.0.1-SNAPSHOT</version>
 <name>SpringBatchManully Maven Webapp</name>
 <repositories>
  <repository>
   <id>central</id>
   <name>Maven Repository Switchboard</name>
   <layout>default</layout>
   <url>http://repo1.maven.org/maven2</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </repository>
 </repositories>
 <properties>
  <spring.version>4.0.1.RELEASE</spring.version>
 </properties>
 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-framework-bom</artifactId>
    <version>${spring.version}</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>
 <dependencies>
  <!-- Spring dependency -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
  </dependency>

  <!-- Spring Batch dependency -->
  <dependency>
   <groupId>org.springframework.batch</groupId>
   <artifactId>spring-batch-core</artifactId>
   <version>3.0.3.RELEASE</version>
  </dependency>

  <dependency>
   <groupId>org.springframework.batch</groupId>
   <artifactId>spring-batch-infrastructure</artifactId>
   <version>3.0.3.RELEASE</version>
  </dependency>
 </dependencies>
 <build>
  <finalName>SpringBatchManully</finalName>
 </build>
</project>

Here just a basic configuration of Spring MVC in web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 id="WebApp_ID" version="2.5">
 <display-name>Archetype Created Web Application</display-name>
 <!-- Defines the Spring Context loader listener. -->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext.xml</param-value>
 </context-param>
 
 <servlet>
  <servlet-name>spring-mvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>spring-mvc</servlet-name>
  <url-pattern>*.do</url-pattern>
 </servlet-mapping>
</web-app>
in applicationContext.xml will create so many beans then only it will become possible to execute spring batch manually.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:batch="http://www.springframework.org/schema/batch"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:task="http://www.springframework.org/schema/task" 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-2.2.xsd
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.2.xsd
  http://www.springframework.org/schema/mvc 
  http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
  http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.2.xsd">
      
   <mvc:annotation-driven />
   
   <context:component-scan base-package="com.spring.batch" />
     
 <bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
 
 <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
  <property name="transactionManager" ref="transactionManager" />
 </bean>
 
 <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
  <property name="jobRepository" ref="jobRepository" />
 </bean>
 
 <bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

 <bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
  <property name="jobRegistry" ref="jobRegistry" />
 </bean>

 <batch:job id="executeTasklet">
  <batch:step id="trackParamsRuleStep">
   <batch:tasklet>
    <bean class="com.spring.batch.tasklet.ExecuteTasklet">
     <property name="params" ref="params" />
    </bean>
   </batch:tasklet>
  </batch:step>
 </batch:job>
 
 <bean id="params" class="java.util.HashMap" />
</beans>
We will create JobLauncherController to mapping the request.

package com.spring.batch.controller;

import org.codehaus.jettison.json.JSONObject;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;

@Controller
@RequestMapping(value = "/job")
public class JobLauncherController {
 private static final String TIME = "time";
 private static final String JOB_PARAM = "job";

 @Autowired
 private JobLauncher jobLauncher;
 @Autowired
 private JobRegistry jobRegistry;

 @RequestMapping(value = "/joblauncher.do", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
 @ResponseStatus(HttpStatus.ACCEPTED)
 public void launchJob(@RequestBody String info) throws Exception {
  JSONObject parseJson = new JSONObject(info);
  JobParametersBuilder builder = extractParameters(parseJson);
  jobLauncher.run(jobRegistry.getJob(parseJson.getString(JOB_PARAM)),
    builder.toJobParameters());
 }

 private JobParametersBuilder extractParameters(JSONObject parseJson)
   throws Exception {
  JobParametersBuilder builder = new JobParametersBuilder();
  // org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for
  // parameters={job=executeTasklet}. If you want to run this job again, change the parameters.
  builder.addLong(TIME, System.currentTimeMillis()).toJobParameters();
  builder.addString(JOB_PARAM, parseJson.getString(JOB_PARAM));
  return builder;
 }
}

One Tasklet to execute Spring job

package com.spring.batch.tasklet;

import java.util.Map;

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 ExecuteTasklet implements Tasklet {
 private Map params;

 public Map getParams() {
  return params;
 }

 public void setParams(Map params) {
  this.params = params;
 }

 public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
  Map jobParameters = chunkContext.getStepContext().getJobParameters();
  params.clear();
  for(Map.Entry entry : jobParameters.entrySet()) {
   params.put(entry.getKey(), entry.getValue().toString());
  }
  System.out.println("running job "+params); 
  return RepeatStatus.FINISHED;
 }
}
Here is the .jsp file so from here we will call the controller so it will start you job based on whatever request you want to proceed.

<html>
<head>
 <script type="text/javascript" src="jquery-1.10.2.min.js"></script>
 <script type="text/javascript">
  $(function(){
   $("#executeManually").click(function(){
    var data = {"job":"executeTasklet"};
    $.ajax({
     type : "POST",
     datatype : "json",
     url : "/SpringBatchManully/job/joblauncher.do",
     contentType: "application/json; charset=utf-8",
     data : JSON.stringify(data),
     success : function(data, textStatus) {
      alert("executing");
     },
        statusCode: {
      901: function() {
       window.location.href=window.location.href;
      }
     }
    });
   });
  });
 </script>
</head>
<body>
 <input type="button" id="executeManually" value="Execute Manually" />
</body>
</html>
When you click on Execute Manually button it will call JobLauncherController and start executing job so do whatever operation you want to perform 

Here is the output you can see running job {time=1441533491828, job=executeTasklet}

You can download code from GitHub Here
Hope you like it, thanks to read this blog Please share you comments so will update ASAP.


How to execute a Batch Job Manually in Spring ?

What is Spring Batch ? Spring Batch is an open source framework for batch processing. It is a lightweight, comprehensive s...