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;
}
}
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