Is there any possibility to find out, If a job is restarted in Spring Batch?
We do provide some Tasklets without restart-support from spring-batch and has to implement our own proceeding, if job is restarted.
Can't find any possibility in JobRepository, JobOperator, JobExplorer, etc.
Define a JobExplorer bean with required properties
<bean id="jobExplorer"
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="lobHandler" ref="lobHandler"/>
</bean>
Query it with your jobName
List<JobInstance> jobInstances= jobExplorer.getJobInstances(jobName);
for (JobInstance jobInstance : jobInstances) {
List<JobExecution> jobExecutions = jobExplorer.getJobExecutions(jobInstance);
for (JobExecution jobExecution : jobExecutions) {
if (jobExecution.getExitStatus().equals(ExitStatus.COMPLETED)) {
//You found a completed job, possible candidate for a restart
//You may check if the job is restarted comparing jobParameters
JobParameters jobParameters = jobInstance.getParameters();
//Check your running job if it has the same jobParameters
}
}
}
Did not compile this but I hope it gives an idea
Another way using jobExplorer is execute the following command:
jobExplorer.getJobExecutions(jobExplorer.getJobInstance(currentJobExecution.getJobInstance().getId())).size() > 1;
This statement verifies if another execution of the the same job (same id) exists. In environments with minimum control, does not exist possibility that the other execution be not a failed or stopped execution.
Potentially you can find this information in spring-batch's database tables, can't remeber the exact table's name, but you can figure out quickly because there are only few tables. I guess there is some information regarding restarting.
Related
I'm using Spring Batch with Spring cloud tasks. I have the following configuration in my job:
#Bean
public Job jobDemo(
#Value("${jobname}")String jobName,
JobBuilderFactory jobBuilderFactory,
JobCompletionNotificationListener listener
) {
return jobBuilderFactory.get(jobName)
.incrementer(new RunIdIncrementer())
.preventRestart()
.listener(listener)
.flow(stepA())
.end()
.build();
}
I don't want the restart functionality in the job, that's why I have put .preventRestart(). I want to launch a new job every time the task runs, that is, a new instance of the job to run even when the last time the job has failed or stopped or anything. But I'm getting the following error:
org.springframework.batch.core.repository.JobRestartException: JobInstance already exists and is not restartable
This happens only in the scenarios when the job does not finish sucessfully. Any ideas about the solution?
A JobInstance can only be completed once successfully. When you are starting a Spring Batch job via Spring Boot, Spring Batch handles the logic to increment a JobParameter if there is a JobParametersIncrementer provides (as you have). However...when Spring Batch does that incrementing, it only increments if the previous job was successful. In your case, you want it to always increment. Because of that, you're going to need to write your own CommandLineRunner that always increments the JobParameters.
Spring Boot's JobLauncherCommandLineRunner is where the code to launch a job exists. You'll probably want to extend that and override it's execute method to be sure job parameters are always incremented.
I am using spring batch, but due to job instance already exist error I need to add current time in my job parameter. I am unable to figure out where to add job parameters. Here is my code:
<step id="myStep">
<tasklet>
<chunk reader="myReader" processor="myProcessor" writer="myWriter" commit-interval="6000" skip-limit="9000">
//some more code.
</chunk>
</tasklet>
</step>
<bean id="myReader" class="org.springframework,batch.item.database.StoredProcedueItemReader" scope="step">
//define property for datasource , procedurename , rowmapper, parameters
<property name="preparedStatementSetter" ref="myPreparedStatmentSetter">
</bean>
<bean id="myPreparedStatmentSetter" class="com.mypackage.MyPreparedStatementSetter" scope="step">
<property name="kId" value="#{jobParameters[kId]}">
</bean>
When I try to run the job for same kId multiple times I get The job already exist error, so I need to add current timestamp to my job parameter.
Would adding current time stamp as a property in the bean myPreparedStatmentSetter be sufficient, or do I need to add jobparameter somewhere else too? From where exactly are jobparameters picked from in spring file?
In case I need to add timestamp to the bean here is a questions -My stored procedure takes only kID as paramter, I dont need to pass current time stamp to stored procedure, then why I need to add the same in myPreparedStatmentSetter.
Also how would I add current timestamp in an xml file without java code?
EDIT
Here is my jobLauncher bean
<bean Id= "jobLauncher "class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" value="myJobRepo">
</bean>
Adding a "random" job parameter by hand, while it can work, isn't the most ideal way to get around the job instance already exists error. Instead, you should consider adding a JobParametersIncrementer to your job. Spring provides the RunIdIncrementer as an implementation of this out of the box. A job configured with it would look something like the following:
#Bean
public Job myJob() {
return jobBuilderFactory.get("myJob")
.incrementer(runIdIncrementer())
.start(step1())
.build();
}
#Bean
public JobParametersIncrementer runIdIncrementer() {
return new RunIdIncrementer();
}
I am guessing that you already adding KId to your job parameters. Add following to your joblaucher.run() method.
new JobParametersBuilder()
.addLong("time",System.currentTimeMillis())
.addLong("KId",<your KID>)
.toJobParameters();
I want to implement a Job similar to the following one, using Spring Batch:
Step1 -----> Step2 --> End
^ |
| |
------------
On some condition in Step2, determined by a custom ExitCode of Step2, either Step1 is started again and thus after it, Step2 again, or the processing will end.
What I've imagined is something like this:
return jobBuilderFactory.get("jobName").incrementer(new RunIdIncrementer())
.start(step1()).next(step2())
.on("condition1").end()
.from(step1())
.on("condition2").to(step1()).end().build();
But obviously, after Step1 was processed through the condition2 of Step2, Step2 won't be started again.
How can I implement such a recursive Batch Processing?
EDIT: I managed to get a solution, however, I do not know yet if it's just a dirty one, because it seems to be just to easy:
jobBuilderFactory.get("jobName")
.flow(step1())
.next(step2())
.on("launchStep1Again")
.to(step1())
.from(step2())
.on("endJobExecution")
.end().build().build();
So the simple change from using the Fluent API's .start() method to its .flow() method seems to do the trick.
SpringBatch was not intended for handling recursive loops. Whilst you may be able to hack around it, you will run into problems when it comes to failures and restarting.
SpringBatch stores the current "position" of it readers and writers of a step inside its execution context; in order to be able to restart the job at the very position it crashed the last time.
Therefore, in case of a restart of the job, SpringBatch will assume that this was the first execution of the job.
Depending on your job structure, this may not be a problem, but it is something you have to keep in mind.
In my opinion, a better solution would be to implement handling the loop outside of the job, in a separate class that handles starting job.
The answers to the following two questions may help in order to come up with such a solution:
Make a spring-batch job exit with non-zero code if an exception is thrown
Reset state before each Spring scheduled (#Scheduled) run
I think you can extend the StepExecutionListenerSupport class and modify you configuration as follows:
<step id="step1" next="step2">
<tasklet ref="step1Tasklet" allow-start-if-complete="true"/>
</step>
<step id="step2">
<tasklet ref="step2Tasklet"/>
<listeners>
<listener ref="myListener"/>
</listeners>
<next on="Back" to="step1"/>
</step>
public class MyListener extends StepExecutionListenerSupport {
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
if(wantToLoop) {
return new ExitStatus("Back");
} else {
return stepExecution.getExitStatus();
}
}
}
We have created a simple spring batch job with single step. There are custom implemented ItemReader and ItemWriter. The ItemReader gets the initial data from job parameter. The batch runs perfectly when run as a standalone java process. But what we want is to host the batch on some server. Therefore, we have created REST service to initialize the batch. The service calls the job URL and passes some parameter. This parameter is passed as job parameter to the batch. The service and job run fine when it is called for one parameter.
But when we call the service more than once (twice for testing purpose), the batch behaves strangely. We are passing different job parameters. But when the execution starts for second job initialization, the job parameter value which is received by the ItemReader is the same as the one for the first execution. And both execution interfere with each other, sharing database connection, interfering with data retrieved etc.
We have tried setting the restartable parameter to false but it didn't work. We have also tried the following solution:
Can we create multiple instances of a same java(spring) batch job?
The above solution started giving "Interrupted attempting lock" error in JBoss.
On further investigation we found that ItemReader is getting initialized only once. That is why it is getting same job parameter value and is interfering with the previous execution.
EDIT
Following is the job configuration:
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<job id="jobid" restartable="false">
<step id="step1">
<tasklet>
<chunk reader="reader" writer="writer"
commit-interval="2">
</chunk>
</tasklet>
</step>
</job>
Following is the code snippet to launch the job:
JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
Job job = (Job) context.getBean("jobid");
try {
JobParameters param = new JobParametersBuilder().addString("key","value").toJobParameters();
JobExecution execution = jobLauncher.run(job, param);
} catch (Exception e) {
e.printStackTrace();
}
Can anyone please suggest some solution? Am I missing some configuration for the step?
Thanks in advance.
I found that if we create the Context and JobLauncher objects statically, that is, if there is only one instance of these two objects, the above thing can work. In this way, we can launch the same job multiple times, but with different parameters.
Class MyClass{
private static ConfigurableApplicationContext context = null;
private static JobLauncher jobLauncher = null;
static{
String[] springConfig = {BatchTokeniserConstants.SPRING_CONFIG_FILE_NAME};
try {
context = new ClassPathXmlApplicationContext(springConfig);
jobLauncher = (JobLauncher) context.getBean("jobLauncher");
BatchTokeniserUtils.loadSystemVaiables();
} catch (BeansException e) {
}
}
}
Now the jobLauncher can be used to launch any job any number of time.
I hope it helps others.
I have a Spring-Batch job that I launch from a Spring MVC controller. The controller gets an uploaded file from the user and the job is supposed to process the file:
#RequestMapping(value = "/upload")
public ModelAndView uploadInventory(UploadFile uploadFile, BindingResult bindingResult) {
// code for saving the uploaded file to disk goes here...
// now I want to launch the job of reading the file line by line and saving it to the database,
// but I want to launch this job in a new thread, not in the HTTP request thread,
// since I so not want the user to wait until the job ends.
jobLauncher.run(
jobRegistry.getJob(JOB_NAME),
new JobParametersBuilder().addString("targetDirectory", folderPath).addString("targetFile", fileName).toJobParameters()
);
return mav;
}
I've tried the following XML config:
<job id="writeProductsJob" xmlns="http://www.springframework.org/schema/batch">
<step id="readWrite">
<tasklet task-executor="taskExecutor">
<chunk reader="productItemReader" writer="productItemWriter" commit-interval="10" />
</tasklet>
</step>
</job>
<bean id="taskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="5" />
</bean>
...but it seems like the multithreading happens only within the job boundaries itself. I.e., the controller thread waits until the job ends, and the job execution is handled by multiple threads (which is good but not the main thing I wanted). The main thing I wanted is that the job will be launched on a separate thread (or threads) while the controller thread will continue its execution without waiting for the job threads to end.
Is there a way to achieve this with Spring-batch?
The official documentation describes your exact problem and a solution in 4.5.2. Running Jobs from within a Web Container:
[...] The controller launches a Job using a JobLauncher that has been configured to launch asynchronously, which immediately returns a JobExecution. The Job will likely still be running, however, this nonblocking behaviour allows the controller to return immediately, which is required when handling an HttpRequest.
Spring Batch http://static.springsource.org/spring-batch/reference/html-single/images/launch-from-request.png
So you were pretty close in trying to use TaskExecutor, however it needs to be passed to the JobLauncher instead:
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<property name="taskExecutor" ref="taskExecutor"/>
</bean>
Disclaimer: I have never used Spring Batch...
The jobLauncher.run() method can be called in a new Thread like so:
#RequestMapping(value = "/upload")
public ModelAndView uploadInventory(UploadFile uploadFile, BindingResult bindingResult) {
[...]
final SomeObject jobLauncher = [...]
Thread thread = new Thread(){
#Override
public void run(){
jobLauncher.run([...]);
}
};
thread.start();
return mav;
}
The thread.start() line will spawn a new thread, and then continue to execute the code below it.
Note that, if jobLauncher is a local variable, it must be declared final in order for it to be used inside of the anonymous Thread class.
If you don't need to show the processing errors to your client, you can start the spring batch job in a seperate thread.