spring-bootspring-batchspring-batch-job-monitoring

How to run same spring batch job at same time from multiple instances of application


I have created one spring batch job which get executed using scheduler on defined time. We have multiple instances of same application, so same job get executed at same time from multiple instances. All application uses same database. Due to that I am getting below error

Stack Trace: org.springframework.transaction.TransactionSystemException: Could not commit JDBC transaction; nested exception is org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions Detail: Reason code: Canceled on identification as a pivot, during commit attempt. Hint: The transaction might succeed if retried. at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:335) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.batch.core.repository.support.AbstractJobRepositoryFactoryBean$1.invoke(AbstractJobRepositoryFactoryBean.java:181) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) at com.sun.proxy.$Proxy209.createJobExecution(Unknown Source) at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:137)

I have stated job using below code-

JobParameters params = new JobParametersBuilder()
            .addString("JobID", UUID.randomUUID().toString(), true)
            .toJobParameters();
jobLauncher.run(job, params);

and did below configuration in yaml file

batch:
job:
  enabled: false
initialize-schema: always
repository:
  isolationlevelforcreate: ISOLATION_READ_COMMITTED

I have added listener to check any job running is in STARTED state and if any running job found , current job get stopped

public class SingleInstanceListener implements JobExecutionListener {
@Autowired
private JobExplorer explorer;

@Override
public void beforeJob(JobExecution jobExecution) {
    String jobName = jobExecution.getJobInstance().getJobName();
    Set<JobExecution> executions = explorer.findRunningJobExecutions(jobName);
    if(executions.size() > 1) {
        jobExecution.stop();
    }
}

@Override
public void afterJob(JobExecution jobExecution) {

}

}

But still I am getting above error. If there is only one instance of application then job running fine. Is there any way to run same job on same time from multiple instances of application or allow only one job to run from any one of the application.


Solution

  • The isolation level is not taken into account as you expect with that property. To customize the isolation level, you need to provide a JobRepository through a BatchConfigurer. Here is a quick example:

    @Configuration
    @EnableBatchProcessing
    public class MyJobConfig extends BasicBatchConfigurer {
    
        @Override
        protected JobRepository createJobRepository() throws Exception {
            JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean();
            repositoryFactoryBean.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED");
            // set other properties on the factory bean
            repositoryFactoryBean.afterPropertiesSet();
            return repositoryFactoryBean.getObject();
        }
    }
    

    This is explained in the documentation here: Transaction Configuration for the JobRepository.

    Note: BasicBatchConfigurer is from Spring Boot. If you use Spring Batch without Spring Boot, you can extend DefaultBatchConfigurer instead.