I have a small sample Spring Batch application that when started for the first time will work fine but whenever I shut the application down & restart the jar I always get this error:
Caused by: org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [INSERT into BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION) values (?, ?, ?, ?)]; Duplicate entry '1' for key 1; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 1
I'm not sure if I have the job incrementer setup wrong. But like I said I can start it up & then use the web service url, /jobLauncher.html
, to invoke the batch process any number of times just fine. It's only after I shutdown the application & restart it that I get this error. It wants to use id 1 for the job execution table but id 1 is already there from the previous runs.
Main class
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, new String[]{ "date=" + System.currentTimeMillis() });
}
}
Webservice class
@Controller
public class JobLauncherController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job job;
@RequestMapping("/jobLauncher.html")
@ResponseBody
public String handle() throws Exception{
jobLauncher.run(job, new JobParametersBuilder().addString("date", System.currentTimeMillis() + "").toJobParameters());
return "Started the batch...";
}
}
Spring Batch class
@Configuration
@EnableBatchProcessing
public class SampleBatchApplication {
@Autowired
private JobBuilderFactory jobs;
@Autowired
private StepBuilderFactory steps;
@Bean
protected Tasklet tasklet() {
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext context) {
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Job job() throws Exception {
return this.jobs.get("job")
.incrementer(new RunIdIncrementer())
.flow(this.step1())
.end()
.build();
}
@Bean
protected Step step1() throws Exception {
return this.steps.get("step1").tasklet(this.tasklet()).build();
}
@Bean
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
try {
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUsername("test");
ds.setPassword("test");
ds.setUrl("jdbc:mysql://127.0.0.1:3306/spring-batch");
} catch (Exception e) {
e.printStackTrace();
}
return ds;
}
}
Job identification: Spring Batch prevents duplicate and concurrent job executions based on the identity of the job instance. Failure scenario: Spring Batch relies on the job instance’s identity to start a new job execution where the previous one left off. Batch processing is about processing fixed, immutable data sets.
You can automate this, as long as the job execution is shut down gracefully, since this gives Spring Batch a chance to correctly set the job execution’s status to FAILED and set its END_TIME to a non-null value. However, if the job execution fails abruptly, the job execution’s status is still be set to STARTED and its END_TIME is null.
Graceful/Abrupt Shutdown Implication When a Spring Batch job execution fails, you can restart it if the job instance is restartable. You can automate this, as long as the job execution is shut down gracefully, since this gives Spring Batch a chance to correctly set the job execution’s status to FAILED and set its END_TIME to a non-null value.
This class is a wrapper around the Job that needs to be launched and around the JobParameters necessary to launch the Batch job. The following image illustrates the typical Spring Integration message flow in order to start a Batch job.
Found the issue. When using the @EnableAutoConfiguration
annotation from Spring Boot it will invoke the org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
class which will initialize the database from the 'schema-mysql.sql' file.
Inside the schema-mysql.sql file is some code to reset the sequence id's for the batch meta tables which is why I was getting a duplicate key error:
INSERT INTO BATCH_STEP_EXECUTION_SEQ values(0);
INSERT INTO BATCH_JOB_EXECUTION_SEQ values(0);
INSERT INTO BATCH_JOB_SEQ values(0);
The fix was to build the Spring Batch tables separately & then change the @EnableAutoConfiguration
annotation to:
@EnableAutoConfiguration(exclude={BatchAutoConfiguration.class})
so that when the application starts up it will not try to initialize the Spring Batch tables.
To exclude or customize Spring Boot's auto-configuration for other components you can find some docs here: http://projects.spring.io/spring-boot/docs/spring-boot-autoconfigure/README.html
The BatchAutoConfiguration code: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With