Java 手动更改Spring批处理元数据表是个坏主意吗? 背景

Java 手动更改Spring批处理元数据表是个坏主意吗? 背景,java,spring-batch,Java,Spring Batch,我正在使用SpringBatch 2.1.8,并通过CommandLineJobRunner运行作业。例如: java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:lau

我正在使用SpringBatch 2.1.8,并通过
CommandLineJobRunner
运行作业。例如:

java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId
java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId times=0
UPDATE BATCH_JOB_EXECUTION SET END_TIME = SYSTIMESTAMP, STATUS = 'FAILED', EXIT_CODE = 'FAILOVER' WHERE JOB_EXECUTION_ID =
    (SELECT MAX(JOB_EXECUTION_ID) FROM BATCH_JOB_EXECUTION WHERE JOB_INSTANCE_ID =
        (SELECT MAX(JOB_INSTANCE_ID) FROM BATCH_JOB_INSTANCE WHERE JOB_NAME = 'XXX'));
问题 在某些情况下(如服务器崩溃),运行的作业可能会中断。 但是被中断的作业在Spring批处理元数据表中留下了
已启动
状态,无法再次运行

org.springframework.batch.core.repository.JobExecutionAlreadyRunningException: A job execution for this job is already running
我可以想出两种解决办法:

解决方案1 添加一个新的作业参数,并每次更改它,使其成为Spring批处理的“新”作业。例如:

java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId
java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId times=0
UPDATE BATCH_JOB_EXECUTION SET END_TIME = SYSTIMESTAMP, STATUS = 'FAILED', EXIT_CODE = 'FAILOVER' WHERE JOB_EXECUTION_ID =
    (SELECT MAX(JOB_EXECUTION_ID) FROM BATCH_JOB_EXECUTION WHERE JOB_INSTANCE_ID =
        (SELECT MAX(JOB_INSTANCE_ID) FROM BATCH_JOB_INSTANCE WHERE JOB_NAME = 'XXX'));
当需要重新运行它时,请清除所有相应的输出数据,计算
次数
一次,然后重新运行作业

解决方案2 手动更改Spring批处理元数据表

更新状态以使作业可重新启动。例如:

java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId
java org.springframework.batch.core.launch.support.CommandLineJobRunner classpath:launchContext.xml theJobId times=0
UPDATE BATCH_JOB_EXECUTION SET END_TIME = SYSTIMESTAMP, STATUS = 'FAILED', EXIT_CODE = 'FAILOVER' WHERE JOB_EXECUTION_ID =
    (SELECT MAX(JOB_EXECUTION_ID) FROM BATCH_JOB_EXECUTION WHERE JOB_INSTANCE_ID =
        (SELECT MAX(JOB_INSTANCE_ID) FROM BATCH_JOB_INSTANCE WHERE JOB_NAME = 'XXX'));
我试过了,似乎效果不错

问题: 解决方案2是个坏主意吗?有陷阱吗


提前谢谢。欢迎使用任何其他解决方案。

解决方案2是目前公认的方法。API没有提供修复此场景的方法。过去有人要求框架自动清理,但99%的情况下,需要人工决定是否确实需要清理


对于选项2,我唯一要注意的是检查BATCH_STEP_EXECUTION表,看看最后执行的步骤处于什么状态。

我为此创建了一个特定的spring bean,它在容器刷新时触发(这也发生在应用程序(重新)启动时)

它搜索“正在运行”的作业,将它们标记为“失败”,然后重新启动它们

import java.util.Date;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class BatchJobRestarter implements ApplicationListener<ContextRefreshedEvent> {

    private static final Logger LOGGER  = LoggerFactory.getLogger(BatchJobRestarter.class);

    @Autowired
    private JobExplorer         jobExplorer;

    @Autowired
    JobRepository               jobRepository;

    @Autowired
    JobOperator                 jobOperator;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        LOGGER.info("Container restart: restarting 'running' batch jobs");
        List<String> jobs = jobExplorer.getJobNames();
        for (String job : jobs) {
            Set<JobExecution> runningJobs = jobExplorer.findRunningJobExecutions(job);

            for (JobExecution runningJob : runningJobs) {
                try {
                    LOGGER.info("Restarting job {} with parameters {}", runningJob.getJobInstance().getJobName(), runningJob.getJobParameters().toString());
                    runningJob.setStatus(BatchStatus.FAILED);
                    runningJob.setEndTime(new Date());
                    jobRepository.update(runningJob);
                    jobOperator.restart(runningJob.getId());
                } catch (Exception e) {
                    LOGGER.error(e.getMessage(), e);
                }
            }
        }
    }
}
import java.util.Date;
导入java.util.List;
导入java.util.Set;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入org.springframework.batch.core.BatchStatus;
导入org.springframework.batch.core.JobExecution;
导入org.springframework.batch.core.explore.JobExplorer;
导入org.springframework.batch.core.launch.JobOperator;
导入org.springframework.batch.core.repository.JobRepository;
导入org.springframework.beans.factory.annotation.Autowired;
导入org.springframework.context.ApplicationListener;
导入org.springframework.context.event.ContextRefreshedEvent;
导入org.springframework.stereotype.Component;
@组成部分
公共类BatchJobRestarter实现ApplicationListener{
私有静态最终记录器Logger=LoggerFactory.getLogger(BatchJobRestarter.class);
@自动连线
私人JobExplorer;
@自动连线
作业库作业库;
@自动连线
作业操作员作业操作员;
@凌驾
ApplicationEvent(ContextRefreshedEvent ContextRefreshedEvent)上的公共无效{
info(“容器重新启动:重新启动“正在运行”的批处理作业”);
List jobs=jobExplorer.getJobNames();
for(字符串作业:作业){
设置runningJobs=jobExplorer.findRunningJobExecutions(作业);
for(作业执行运行作业:运行作业){
试一试{
info(“使用参数{}重新启动作业{}”,runningJob.getJobInstance().getJobName(),runningJob.getJobParameters().toString());
runningJob.setStatus(BatchStatus.FAILED);
runningJob.setEndTime(新日期());
jobRepository.update(运行作业);
重新启动(runningJob.getId());
}捕获(例外e){
LOGGER.error(e.getMessage(),e);
}
}
}
}
}

Steef

我对此也很好奇,我也使用了很多解决方案2:)@dimzak到目前为止,你发现解决方案2有什么问题吗?还没有,但我们没有那么多工作,而且大多数工作都很简单,所以采用解决方案2更容易