I have a Spring Batch (3.0.6) / Spring Batch Admin (2.0 snapshot) application that I'm attempting to schedule jobs with using Quartz (2.2.2). Using batch admin config for hsqldb (we don't care about this data at all) we're getting a multiple transaction change error when running the Quartz jobs (though the Spring Batch jobs are kicked off and completing fine).
I receive the following error:
[2016-05-02 16:28:16.005] boot - 48230 INFO [scheduler_Worker-2] --- MyJobBean: Launching myBatchJob
[2016-05-02 16:28:16.014] boot - 48230 WARN [scheduler_Worker-1] --- MyBatchJob: Job failed with Exception PreparedStatementCallback; SQL [INSERT into BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION) values (?, ?, ?, ?)]; transaction rollback: serialization failure; nested exception is java.sql.SQLTransactionRollbackException: transaction rollback: serialization failure
java.sql.SQLTransactionRollbackException: transaction rollback: serialization failure
at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
at org.hsqldb.jdbc.JDBCPreparedStatement.fetchResult(Unknown Source)
at org.hsqldb.jdbc.JDBCPreparedStatement.executeUpdate(Unknown Source)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:873)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:866)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:629)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:866)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:927)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:932)
at org.springframework.batch.core.repository.dao.JdbcJobInstanceDao.createJobInstance(JdbcJobInstanceDao.java:115)
at org.springframework.batch.core.repository.support.SimpleJobRepository.createJobExecution(SimpleJobRepository.java:135)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.batch.core.repository.support.AbstractJobRepositoryFactoryBean$1.invoke(AbstractJobRepositoryFactoryBean.java:172)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
at com.sun.proxy.$Proxy61.createJobExecution(Unknown Source)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:125)
at org.springframework.batch.core.launch.JobLauncher$run.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:130)
at com.app.MyBatchJob.performJob(MyBatchJob.groovy:41)
at com.app.MyBatchJob$performJob.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:114)
at com.app.MyJobBean.executeInternal(MyJobBean.groovy:36)
at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75)
at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: org.hsqldb.HsqlException: transaction rollback: serialization failure
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.Session.executeCompiledStatement(Unknown Source)
at org.hsqldb.Session.execute(Unknown Source)
... 41 more
Caused by: org.hsqldb.HsqlException: transaction rollback: row change by multiple transactions
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.TransactionManagerMVCC.addInsertAction(Unknown Source)
at org.hsqldb.Session.addInsertAction(Unknown Source)
at org.hsqldb.Table.insertSingleRow(Unknown Source)
at org.hsqldb.StatementDML.insertSingleRow(Unknown Source)
at org.hsqldb.StatementInsert.getResult(Unknown Source)
at org.hsqldb.StatementDMQL.execute(Unknown Source)
... 43 more
Caused by: org.hsqldb.HsqlException: integrity constraint violation: unique constraint or index violation; JOB_INST_UN table: BATCH_JOB_INSTANCE
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.Constraint.getException(Unknown Source)
at org.hsqldb.index.IndexAVLMemory.insert(Unknown Source)
at org.hsqldb.persist.RowStoreAVL.indexRow(Unknown Source)
... 49 more
I've boiled it down to this simple config (read a list of items, convert to upper case and print them) and I still get this error.
The job configuration in groovy...
@Bean
public ItemProcessor<Object, Object> processor() {
return new ItemProcessor<Object, Object>() {
@Override
Object process(Object item) throws Exception {
return item.toString().toUpperCase()
}
}
}
@Bean
public ItemReader<Object> reader() {
return new ListItemReader<Object>(['John', 'Jill', 'James', 'Jenny'])
}
@Bean
public ItemWriter<Object> writers() {
return new ItemWriter<Object>() {
@Override
void write(List<?> items) throws Exception {
items.each {
println("List item : $it")
}
}
}
}
@Bean
public Job myJob() {
return jobBuilderFactory.get("myJob")
.incrementer(new RunIdIncrementer())
.flow(myStep1())
.end()
.build()
}
@Bean
public Step myStep1() {
return stepBuilderFactory.get("myStep1")
.<Object, Object> chunk(10)
.reader(reader())
.processor(processor())
.writer(writers())
.build()
}
Here's the quartz bean config (cron schedule for every 15 minutes):
@Bean
public SchedulerFactoryBean scheduler() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean()
schedulerFactoryBean.setTriggers(myTrigger().getObject())
schedulerFactoryBean.start()
return schedulerFactoryBean
}
@Bean
public CronTriggerFactoryBean myTrigger() {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean()
cronTriggerFactoryBean.setCronExpression("0 0/15 * * * ?")
cronTriggerFactoryBean.setJobDetail(myJobFactory().getObject())
return cronTriggerFactoryBean
}
@Bean
public JobDetailFactoryBean myJobFactory() {
JobDetailFactoryBean factory = new JobDetailFactoryBean(jobClass: MyJobBean,
name: MyJobBean.JOB_NAME)
return factory
}
The QuartzJobBean :
@Log4j
@DisallowConcurrentExecution
class MyJobBean extends QuartzJobBean {
public static final JOB_NAME = "myBatchJob"
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("Launching $JOB_NAME")
ApplicationContext applicationContext = ApplicationContextUtil.getAppContext()
MyBatchJob myBatchJob = applicationContext.getBean(JOB_NAME, MyBatchJob.class)
try {
myBatchJob.performJob()
} catch (Exception e) {
log.warn("Quartz job failed with cause $e.message", e)
}
}
}
The class that launches a batch job via the JobLauncher and JobLocator of spring batch:
@Log4j
@Component
class MyBatchJob {
@Autowired
private JobLocator jobLocator
@Autowired
private JobLauncher jobLauncher
public void performJob() {
try {
JobParameter timestampParam = new JobParameter(System.currentTimeMillis())
def paramMap = [:]
paramMap.put("timestamp", timestampParam)
JobExecution jobResult = jobLauncher.run(jobLocator.getJob("myJob"), new JobParameters(paramMap))
log.info("Job launched with result $jobResult")
} catch (Exception e) {
log.warn("Job failed with Exception ${e.getMessage()}", e.getCause())
}
}
}
Relevant properties for spring batch admin:
batch.job.configuration.package=com.app.jobs
batch.business.schema.script=classpath:business-schema-hsqldb.sql
ENVIRONMENT=hsqldb
The quartz job fails with an Exception but the Spring Batch job runs and completes just fine. I'm at about my wits end as to fixing the Quartz job. Anyone see anything I'm doing wrong with my configuration ?
UPDATE:
When I remove Spring Batch Admin, this error goes away. If I switch from @EnableBatchAdmin back to @EnableBatchProcessing the exception does not get thrown. Now, I'm trying to figure out what between the default config of batch vs batch admin is causing this problem.