Spring Batch和Quartz调度,由多个事务进行行更改

时间:2016-05-02 22:18:20

标签: spring-batch quartz-scheduler spring-batch-admin

我有一个Spring Batch(3.0.6)/ Spring Batch Admin(2.0快照)应用程序,我正在尝试使用Quartz(2.2.2)安排作业。使用hsqldb的批量管理配置(我们根本不关心这些数据)我们在运行Quartz作业时遇到多个事务更改错误(尽管Spring Batch作业已启动并完成正常)。

我收到以下错误:

    [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

我把它归结为这个简单的配置(读取项目列表,转换为大写并打印它们),我仍然得到这个错误。 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()
}

这是石英豆配置(每15分钟一次的cron计划):

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

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

通过弹出批次的JobLauncherJobLocator启动批处理作业的类:

@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())
        }


    }

}    

spring batch admin的相关属性:

batch.job.configuration.package=com.app.jobs
batch.business.schema.script=classpath:business-schema-hsqldb.sql

ENVIRONMENT=hsqldb

石英作业因异常而失败,但Spring Batch作业运行并完成就好了。我正在考虑修复Quartz工作。任何人都看到我的配置出错了吗?

更新 当我删除Spring Batch Admin时,此错误消失了。如果我从@EnableBatchAdmin切换回@EnableBatchProcessing,则不会抛出异常。现在,我试图弄清楚批处理与批处理管理的默认配置之间的原因是什么。

2 个答案:

答案 0 :(得分:0)

在我看来,new RunIdIncrementer()行每次都会创建新的Incrementer,每次都会产生1个id,因此您在BATCH_JOB_INSTANCE表中获得了唯一约束的异常。 您需要将其存储在某个位置并且每次都可以访问它。另请参阅this answer

答案 1 :(得分:0)

JobRepository的隔离级别是多少?如果当前ISOLATION_READ_COMMITTED(默认为ISOLATION_SERIALIZABLE),您可能希望将其降至ISOLATION_SERIALIZABLE

<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean" 
    p:dataSource-ref="dataSource"
    p:transactionManager-ref="transactionManager">
    <property name="tablePrefix" value="${batch.schema}.BATCH_" />
    <property name="isolationLevelForCreate" value="ISOLATION_READ_COMMITTED" />
</bean>