前言
在上一篇文章中,我们主要介绍了batch的各个组件的作用,并且写了一个简单的demo。在这一篇中,我们主要学习一下Job的使用。
Job的创建
从上一篇中我们可以看到job是由JobBuilderFactory 创建。
step
我们知道一个job可以由一个或多个step组成,上一篇中我们只写了一个step,这里我们多加几个step。
多个step
这里我们在上一篇demo的基础上再写一个2个step:
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job demoJob1() {
// 通过JobBuilderFactory 创建
return jobBuilderFactory.get("demoJob1")
.start(demoStep1())
.next(demoStep2())
.next(demoStep3())
.build();
}
@Bean
public Step demoStep1() {
// 通过StepBuilderFactory 创建
return stepBuilderFactory.get("demoStep1").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep1 ======");
return RepeatStatus.FINISHED;
}
}).build();
}
@Bean
public Step demoStep2() {
return stepBuilderFactory.get("demoStep2").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep2 ======");
return RepeatStatus.FINISHED;
}
}).build();
}
@Bean
public Step demoStep3() {
return stepBuilderFactory.get("demoStep3").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep3 ======");
return RepeatStatus.FINISHED;
}
}).build();
}
}
第一次启动项目运行:
可以看到之前运行过的step1不会再执行,执行了step2和step3。
这是因为,Spring Batch中相同的Job,当所带参数一致的时候,有且只会启动一次。
我们看看数据库中:
job:
step:
第二次启动执行:
查看日志:
数据库中:
job:
step:
从数据库中我们可以看到step的status状态都是complete即完成的状态。
但是往往在业务中,我们可以通过判断step的状态来决定下一步我们需要做什么。
step的状态
我们修改job调用的配置,使它变成有状态判定的调用:
@Bean
public Job demoJob1() {
return jobBuilderFactory.get("demoJob1")
.start(demoStep1())
// .next(demoStep2())
// .next(demoStep3())
.on("COMPLETED").to(demoStep4())
.from(demoStep4()).on("COMPLETED").to(demoStep5())
.from(demoStep5()).end()
.build();
}
@Bean
public Step demoStep4() {
return stepBuilderFactory.get("demoStep4").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep4 ======");
return RepeatStatus.FINISHED;
}
}).build();
}
@Bean
public Step demoStep5() {
return stepBuilderFactory.get("demoStep5").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep5 ======");
return RepeatStatus.FINISHED;
}
}).build();
}
只有在step1返回的状态时COMPLETED时,才会往下调用。
日志:
Step的内部 – Tasklet和Chunk
上面的例子我们的是通过创建实现Tasklet的对象,并重写其中的excute方法来完成的step的执行
return stepBuilderFactory.get("demoStep2").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep2 ======");
return RepeatStatus.FINISHED;
}
}).build();
这里我们使用另一种方式,通过chunk来完成step的执行:
@Bean
public Step demoStep17() {
AtomicInteger i = new AtomicInteger(0);
// chunk(2)执行2次
return stepBuilderFactory.get("demoStep15").chunk(2).reader(new ItemReader<Object>() {
@Override
public Object read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
System.out.println("===========demoStep17 reader ============");
i.addAndGet(1);
if (i.get() > 3)
return null;
return "read";
}
}).processor(new ItemProcessor<Object, Object>() {
@Override
public Object process(Object o) throws Exception {
System.out.println("===========demoStep17 process ============");
return "process";
}
}).writer(new ItemWriter<Object>() {
@Override
public void write(List<?> list) throws Exception {
System.out.println("===========demoStep17 write ============");
}
}).build();
}
运行结果:
第四次执行reader时,返回null,退出了这个step,虽然退出了,但还是把第三次的process和writer执行完了。
具体见:https://blog.csdn.net/neweastsun/article/details/88866837
Job的触发
在上一篇中,我们在配置文件中将项目启动时自动执行job设置为了true,但是更多的时候,我们希望job的触发是被我们控制的。这里便用到了jobLauncher执行器。
#是否启动项目时候,自动执行job
#spring.batch.job.enabled = false
JobLauncher
通过JobLauncher,我们可以通过调用api的方式,来启动我们指定的job。
(当然我们也可以用定时器触发,这里我们先不讲,后面结合quartz使用时我们再说)
首先定义job和step
@Bean
public Job demoJob2() {
return jobBuilderFactory.get("demoJob2")
.start(demoStep10()).build();
}
@Bean
public Step demoStep10() {
return stepBuilderFactory.get("demoStep10").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("======= demoStep10 ======");
return RepeatStatus.FINISHED;
}
}).build();
}
定义api接口调用
@RestController
public class JobController1 {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job demoJob2;
@GetMapping("/launcherJob")
public String runJob1(String param) throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
System.out.println("========== Job Launcher run with param :" + param);
JobParameters jobParameters = new JobParametersBuilder().addString("param", param).toJobParameters();
jobLauncher.run(demoJob2, jobParameters);
return "========== Run Job2 Success ==========";
}
}
客户端调用:
控制台输出:
查看数据库的batch_job_execution_params表可以看到KEY_NAME和STRING_VAL字段有我们传入的值
quartz + JobLauncher
我们可以通过quartz,在quartz的job中通过JobLauncher触发batch的job:
public abstract class TuiJob extends QuartzJobBean {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobLocator jobLocator;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis())
.toJobParameters();
try {
Job job = jobLocator.getJob(getJobName());
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
jobExecution.getStatus();
} catch (NoSuchJobException | JobExecutionAlreadyRunningException | JobRestartException
| JobInstanceAlreadyCompleteException | JobParametersInvalidException e) {
e.printStackTrace();
}
}
// 通过实现该方法,定制化启动不同的batch job
protected abstract String getJobName();
}