Spring Boot Batch Restart Job Example

Tags: springboot, springbatch, restartjob
Published: 2021-03-05 15:17:56

https://loizenai.com/spring-boot-batch-restart-job/

- Tutorial: "Spring Boot Batch Restart Job Example with SpringBoot RestAPI Implementation"

SpringBatch provides a restartable mechanics with a Job. So in the tutorial, I will present to you the principle of this function with SpringBoot.

<!--more-->
Technologies:

– Java 1.8
– Maven
– Spring Boot
– Spring Tool Suite
<h2 id="overview-spring-boot-batch-restart-job">Overview - Spring Boot Batch Restart Job Example</h2>
Create a Batch Job which has a simple step with a Reader & Writer.

- Reader reads a list file-paths then pass them to a Writer. The Write uses file paths to process content of each file. 
We creat a Batch Job, then we test a restartable function base on FileNotFound Error.

- Note: a Batch Job is created with restartable default. Spring Batch also supports configuration to prevent the restartable function.
<h2 id="project-structure-spring-boot-batch-restart">Project Structure - Spring Boot Batch Restart Job Example</h2>
[caption id="attachment_4892" align="alignnone" width="687"]<img src="https://loizenai.com/wp-content/uploads/2021/01/Spring-Boot-Springbatch-Restartable-Project-Structure.png" alt="Spring Boot Springbatch Restartable Project Structure - Spring Boot Batch Restart Job Example" width="687" height="548" class="size-full wp-image-4892" /> Spring Boot Springbatch Restartable Project Structure[/caption]

* Step to do:

- Create SpringBoot project
- Create simple step
- Config a Batch Job
- Create a Job Launch controller
- Config Batch Job Database
- Enable Batch Job
- Scenario Test
<h2 id="create-spring-boot-project">Create SpringBoot Project</h2>
Open Spring Tool Suite, on main menu, choose File->New->Spring Starter Project, input project info. Press Next then Finish, a Spring Boot project will be created successfully.

- Open <code>pom.xml</code> file, add Web MVC, PostgreSQL and Spring Batch dependencies:

<pre><code class="language-xhtml">
&lt;dependency&gt;
	&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
	&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
	&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
	&lt;artifactId&gt;spring-boot-starter-batch&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- http://mvnrepository.com/artifact/postgresql/postgresql --&gt;
&lt;dependency&gt;
	&lt;groupId&gt;postgresql&lt;/groupId&gt;
	&lt;artifactId&gt;postgresql&lt;/artifactId&gt;
	&lt;version&gt;9.1-901-1.jdbc4&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h2 id="create-reader-writer-for-batch-job-step">Create Reader & Writer for Batch Job Step - Spring Boot Batch Restart Job Example</h6>
Create a <strong>Reader</strong> class:

<pre><code class="language-java">
package com.loizenai.springbatch.step;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
 
public class Reader implements ItemReader<String>{
 
    private String[] files = {"C:\\readfile\\1.txt"};
     
    public static int  count=0;
     
    Logger logger = LoggerFactory.getLogger(this.getClass());
     
    @Override
    public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
         
        if(count < files.length){
            return files[count++];
        }else{
            count=0;
        }
        return null;
    }
}	
</code></pre>

Create a Writer class:

<pre><code class="language-java">
package com.loizenai.springbatch.step;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemWriter;
 
public class Writer implements ItemWriter<String> {
 
    Logger logger = LoggerFactory.getLogger(this.getClass());
     
    @Override
    public void write(List<? extends String> paths) throws Exception {
        for(String filePath : paths){
        	System.out.println("filePath = " + filePath);
            try (Stream<String> stream = Files.lines(Paths.get(filePath))) {
                stream.forEach(System.out::println);
            } catch (IOException e) {
            	Reader.count = 0;
            	throw(e);
            }
        }
    }
    
}</code></pre>
<h2 id="configure-spring-boot-batch-job">Configure a SpringBoot Batch Job</h2>
Config a Simple Batch Job:

<pre><code class="language-java">
package com.loizenai.springbatch.config;
 
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.loizenai.springbatch.step.Reader;
import com.loizenai.springbatch.step.Writer;
 
@Configuration
public class BatchConfig {
 
    @Autowired
    public JobBuilderFactory jobBuilderFactory;
 
    @Autowired
    public StepBuilderFactory stepBuilderFactory;
 
     
    @Bean
    public Job job() {
        return jobBuilderFactory.get("job")
                .incrementer(new RunIdIncrementer())
                .flow(step1())
                .end()
                .build();
    }
 
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<String, String> chunk(1)
                .reader(new Reader())
                .writer(new Writer())
                .build();
    }

}</code></pre>
<h2 id="create-spring-boot-batch-job-launch-controller">Create a SpringBoot Batch Job Launch Controller</h2>
- Create a simple Controller for Job Launching:

<pre><code class="language-java">
package com.loizenai.springbatch.controller;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class JobLauncherController {
 
    @Autowired
    JobLauncher jobLauncher;
 
    @Autowired
    Job job;
 
    @RequestMapping("/launchjob")
    public String handle() throws Exception {
 
        Logger logger = LoggerFactory.getLogger(this.getClass());
        JobExecution jobExecution = null;
        try {
        	jobExecution = jobLauncher.run(job, new JobParameters());
        } catch (Exception e) {
            logger.info(e.getMessage());
        }
 
        return "jobExecution's info: Id = " + jobExecution.getId() + " ,status = " + jobExecution.getExitStatus();
    }
}</code></pre>
<h2 id="configure-spring-boot-batch-job-database">Configure SpringBoot Batch Job Database</h2>
Config DataBase's info for Spring Batch:

<pre>code class="language-java">
spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123
spring.batch.job.enabled=false
</code></pre>
<h2>Enable SpringBoot Batch Job</h2>
In main class of Spring Boot project, use <strong>@EnableBatchProcessing</strong> to enable Spring Batch.

<pre><code class="language-java">
package com.loizenai.springbatch;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableBatchProcessing
public class SpringBatchRestartableFunctionApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBatchRestartableFunctionApplication.class, args);
	}
}</code></pre>
<h2>Scenario Testing - SpringBoot Batch Restartable Function</h2>
- Build & Run the Spring Boot Batch Job.

The Reader will forward a file name: <code>C:\\readfile\\1.txt</code> to Writer. Delete a file <code>C:\\readfile\\1.txt</code>, then makes a launch request: <code>http://localhost:8080/launchjob</code>

A Job Instance is created with Empty Parameters:

<pre><code class="language-java">
Job: [FlowJob: [name=job]] launched with the following parameters: [{}]
</code></pre>

Investigate Server <strong>Logs</strong>:

Result <strong>FAIL</strong>:

<pre><code class="language-java">
Job: [FlowJob: [name=job]] completed with the following parameters: [{}] and the following status: [FAILED]
</code></pre>

Cause:

<pre><code class="language-java">
2017-01-13 20:21:29.216  INFO 11012 --- [nio-8080-exec-1] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
filePath = C:\readfile\1.txt
2017-01-13 20:21:39.781 ERROR 11012 --- [nio-8080-exec-1] o.s.batch.core.step.AbstractStep         : Encountered an error executing step step1 in job job

java.nio.file.NoSuchFileException: C:\readfile\1.txt
	at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79) ~[na:1.8.0_101]
	at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) ~[na:1.8.0_101]
	at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) ~[na:1.8.0_101]
	at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230) ~[na:1.8.0_101]
</code></pre>

- Now create a <strong>C:\readfile\1.txt</strong> with content:

<pre><code class="language-java">
line1
line2
line3
line4
line5</code></pre>

Then restart the <code>Job Instance</code> (with parameters: [{}]) again by <code>http://localhost:8080/launchjob</code>

Result: <code>COMPLETED</code>

<pre><code class="language-java">
Job: [FlowJob: [name=job]] completed with the following parameters: [{}] and the following status: [COMPLETED]
</code></pre>

<pre><code class="language-java">
filePath = C:\readfile\1.txt
line1
line2
line3
line4
line5
</code></pre>

Make the launching request again: <strong>http://localhost:8080/launchjob</strong>, result:

<pre><code class="language-java">
Step already complete or not restartable, so no action to execute: StepExecution: id=1000, version=4, name=step1, status=COMPLETED, exitStatus=COMPLETED, readCount=1, filterCount=0, writeCount=1 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=2, rollbackCount=0, exitDescription=
Job: [FlowJob: [name=job]] completed with the following parameters: [{}] and the following status: [COMPLETED]
</code></pre>
<h2 id="prevent-restartable">Prevent Restartable</h2>
SpringBatch also supports a config to prevent restartable ability of a Job even if a Job Instance's Execution with FAIL status.

How to configure? Just use a little code <code>.preventRestart()</code>, details with <code>BatchConfig</code> class:

<pre><code class="language-java">
package com.loizenai.springbatch.config;
 
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.loizenai.springbatch.step.Reader;
import com.loizenai.springbatch.step.Writer;
 
@Configuration
public class BatchConfig {
 
    @Autowired
    public JobBuilderFactory jobBuilderFactory;
 
    @Autowired
    public StepBuilderFactory stepBuilderFactory;
 
     
    @Bean
    public Job job() {
        return jobBuilderFactory.get("job")
        		.preventRestart()
                .incrementer(new RunIdIncrementer())
                .flow(step1())
                .end()
                .build();
    }
 
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<String, String> chunk(1)
                .reader(new Reader())
                .writer(new Writer())
                .build();
    }

}
</code></pre>

Build & Run Spring Boot project again, delete <strong>C:\\readfile\\1.txt</strong> file.

Make a request: <strong>http://localhost:8080/launchjob</strong>, -> Result: <strong>FAIL</strong> with Log details:

<pre><code class="language-java">
2017-01-13 20:35:08.664  INFO 5092 --- [nio-8080-exec-1] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
filePath = C:\readfile\1.txt
2017-01-13 20:35:21.296 ERROR 5092 --- [nio-8080-exec-1] o.s.batch.core.step.AbstractStep         : Encountered an error executing step step1 in job job

java.nio.file.NoSuchFileException: C:\readfile\1.txt
	at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79) ~[na:1.8.0_101]
	at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) ~[na:1.8.0_101]
	at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) ~[na:1.8.0_101]
	at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230) ~[na:1.8.0_101]
</code></pre>

Create a file: <strong>C:\\readfile\\1.txt</strong>, then makes request <strong>http://localhost:8080/launchjob</strong> again, result <strong>FAIL</strong>:

<pre><code class="language-java">
2017-01-13 20:37:31.672  INFO 5092 --- [nio-8080-exec-3] c.j.s.controller.JobLauncherController   : JobInstance already exists and is not restartable
2017-01-13 20:37:31.693 ERROR 5092 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
	at com.loizenai.springbatch.controller.JobLauncherController.handle(JobLauncherController.java:33) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_101]
</code></pre>
<h2 id="read-more">Read More - Spring Boot Batch Restart Job Example</h2>
<div class="div-loizenai-pre-related-post">
Related posts:
<hr>
<ul class="ul-loizenai-pre-related-post">
<li><a href="https://loizenai.com/spring-boot-spring-batch-integration/">SpringBoot and Spring Batch Integration</a></li>
<li><a href="https://loizenai.com/spring-batch-decider-example/">Spring Batch Decider Example</a></li>
<li><a href="https://loizenai.com/angular-8-springboot-postgresql-crud-example/">Angular 8 SpringBoot PostgreSQL CRUD Example</a></li>
<li><a href="https://loizenai.com/spring-boot-mongodb-angular-11-crud-example/">SpringBoot MongoDB Angular 11 CRUD Example</a></li>
<li><a href="https://loizenai.com/spring-boot-mongodb-angular-crud-example-github/">SpringBoot MongoDB Angular CRUD Example</a></li>
<li><a href="https://loizenai.com/spring-boot-angular-11-pagination-example/">SpringBoot Angular 11 Pagination Example</a></li>
</ul>
</div>

- Reference Link <a href="https://docs.spring.io/spring-batch/docs/current/reference/html/job.html">Configuring and Running a Job - Spring</a>
<h2 id="sourcecode">Sourcecode - Spring Boot Batch Restart Job Example</h2>
<p class="sourcecode-lzai">
<a href="https://loizenai.com/wp-content/uploads/2021/01/Spring-Boot-Batch-Restartable-Function.zip">Spring-Boot-Batch-Restartable-Function</a>
</p>