Spring Batch认为一个Job的唯一依据是 job_name + hash(JobParameters) 作为联合主键 。如果每次执行都会传入一个唯一变动的参数(如时间戳、自增ID等),spring batch 就会认为这是不同的job,这样同一个jobName就可以重复执行了。
@RequestMapping("/start")
public ExitStatus start() throws Exception {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("timestamp", new Date().getTime())
.toJobParameters();
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
return jobExecution.getExitStatus();
}
@Bean
public Tasklet hellWorldTasklet() {
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
Map<String, Object> jobParameters = chunkContext.getStepContext().getJobParameters();
// 1686233766695 hello world spring batch
System.out.println(jobParameters.get("timestamp") + " hello world spring batch");
return RepeatStatus.FINISHED;
}
};
}
每次调用的timestamp值都不一样,都会看做是不同的作业,调用两次: http://localhost:8080/job/start ,两次都是 "exitCode": "COMPLETED"
。
BATCH_JOB_EXECUTION_PARAMS: 记录作业执行参数。
@StepScope
:延时加载Bean,项目启动时不加载Bean,等到实际调用时才去加载该Bean。@Value("#{jobParameters['变量名']}")
:获取jobParameters中指定变量的值。@Bean
public Step hellWorldStep() {
return stepBuilderFactory.get("hellWorldStep")
// 传参传null即可,实际会通过@Value注入的
.tasklet(hellWorldTasklet(null))
.build();
}
@Bean
@StepScope
public Tasklet hellWorldTasklet(@Value("#{jobParameters['timestamp']}") Long timestamp) {
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println(timestamp + " hello world spring batch");
return RepeatStatus.FINISHED;
}
};
}
传参可以在运行作业时通过jobParameters传参,也可以通过命令行方式传参,
注意:命令行方式传参不能通过手动启动作业,必须通过启动程序时自动执行作业才可以获取到参数,即spring.batch.job.enabled = true
。
@Bean
@StepScope
public Tasklet hellWorldTasklet() {
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
// key1:value1
// key2:value2
Map<String, Object> jobParameters = chunkContext.getStepContext().getJobParameters();
return RepeatStatus.FINISHED;
}
};
}
通过往JobParameters增加时间戳参数timestamp是可以解决同一个Job执行多次的问题,但是编程不够优雅,SpringBatch提供了一种自增ID的实现。
RunIdIncrementer会自动往JobParameters中添加一个参数run.id
,值为从1累加的。通过使用RunIdIncrementer也可以做到同一个作业允许重复运行多次。通过 incrementer(JobParametersIncrementer jobParametersIncrementer )
方法设置。
@Bean
public Job helloWorldJob() {
return jobBuilderFactory.get("helloWorldJob")
.start(hellWorldStep())
// 配置作业参数增量器
.incrementer(new RunIdIncrementer())
.build();
}
注意:spring.batch.job.enabled=true时incrementer是直接生效的。
@RequestMapping("/start")
public ExitStatus start() throws Exception {
// 这里传一个空的JobParameters对象即可
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
return jobExecution.getExitStatus();
}
如果spring.batch.job.enabled=false,通过手动触发时需要先借助JobExplorer先获取run.id,然后再通过RunIdIncrementer去自增累加。
@RestController
@RequestMapping("/job")
public class JobController {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobExplorer jobExplorer;
@Autowired
private Job job;
@RequestMapping("/start")
public ExitStatus start() throws Exception {
// RunIdIncrementer 需要配合JobExplorer使用
JobParameters jobParameters = new JobParametersBuilder(jobExplorer)
.getNextJobParameters(job)
.toJobParameters();
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
return jobExecution.getExitStatus();
}
}
BATCH_JOB_EXECUTION_PARAMS表中有2条,同一个作业jobName对应的run.id值是累加的,不同的作业实例会从1开始。
DefaultJobParametersValidator是Spring Batch提供的用于校验参数必选
和可选
的校验,校验不通过抛出异常不执行Job, JobParametersInvalidException: The JobParameters do not contain required keys: [file]
,注意如果配置了RunIdIncrementer,setRequiredKeys也要设置"run.id"
@Bean
public Job helloWorldJob() {
return jobBuilderFactory.get("helloWorldJob")
.start(hellWorldStep())
.incrementer(new RunIdIncrementer())
.validator(defaultJobParametersValidator())
.build();
}
@Bean
public DefaultJobParametersValidator defaultJobParametersValidator() {
DefaultJobParametersValidator defaultJobParametersValidator = new DefaultJobParametersValidator();
defaultJobParametersValidator.setRequiredKeys(new String[]{"run.id", "file"});
defaultJobParametersValidator.setOptionalKeys(new String[]{"date"});
return defaultJobParametersValidator;
}
实现 JobParametersValidator
接口,当不满足条件时抛出异常 JobParametersInvalidException
, 通过validator()
设置校验器。
public class MyJobParametersValidator implements JobParametersValidator {
@Override
public void validate(JobParameters jobParameters) throws JobParametersInvalidException {
Long timestamp = jobParameters.getLong("timestamp");
if (timestamp < new Date().getTime()) {
throw new JobParametersInvalidException("timestamp参数错误");
}
}
}
@Bean
public Job helloWorldJob() {
return jobBuilderFactory.get("helloWorldJob")
.start(hellWorldStep())
.validator(jobParametersValidator())
.build();
}
@Bean
public JobParametersValidator jobParametersValidator() {
return new MyJobParametersValidator();
}
将多个作业参数校验器封装成一个作业校验器,只有所有校验器通过才算整个校验器通过。
@Bean
public Job helloWorldJob() {
return jobBuilderFactory.get("helloWorldJob")
.start(hellWorldStep())
.incrementer(new RunIdIncrementer())
.validator(compositeJobParametersValidator())
.build();
}
@Bean
public CompositeJobParametersValidator compositeJobParametersValidator() {
DefaultJobParametersValidator defaultJobParametersValidator = new DefaultJobParametersValidator();
defaultJobParametersValidator.setRequiredKeys(new String[]{"run.id", "file"});
defaultJobParametersValidator.setOptionalKeys(new String[]{"date"});
MyJobParametersValidator myJobParametersValidator = new MyJobParametersValidator();
CompositeJobParametersValidator compositeJobParametersValidator = new CompositeJobParametersValidator();
compositeJobParametersValidator.setValidators(Arrays.asList(defaultJobParametersValidator, myJobParametersValidator));
return compositeJobParametersValidator;
}
靠体力为生永远都是不可取的,
骆驼祥子在离世的那天,
他以为说多拉几趟车,
就能过上更好的生活。
其实人到了错误的环境下,
接触了错误的人,
遇见了错误的事儿和理论,
这个时候,
即使你再有能力,
即使你再善良,
都没有用的。
其实老人们说的那句话还是很有道理的,
就是:你跟着千万赚百万,你跟着乞丐,你就只能学要饭。