Spring Batch

Spring Batch框架结构

应用层包含所有的批处理作业,通过Spring框架管理成员自定义的代码。

核心层包含了Batch启动和控制所需要的核心类,如JobLauncher、Job和Step等。

基础架构层提供共通的读(ItemReader)、写(ItemWriter)和服务(如RetryTemplate:重试模块,可被核心层和应用层使用)。

Spring Batch 框架流程介绍

   上图描绘了Spring Batch的执行过程。说明如下:

    每个Batch都会包含一个Job。Job就像一个容器,这个容器里装了若干Step,Batch中实际干活的也就是这些Step,至于Step干什么活,无外乎读取数据,处理数据,然后将这些数据存储起来(ItemReader用来读取数据,ItemProcessor用来处理数据,ItemWriter用来写数据) 。可通过调用JobLauncher接口启动一个Job。

JobExecution jobExecution=JobLauncher.run(Job var1, JobParameters var2);

JobRepository是上述处理提供的一种持久化机制,它为JobLauncher,Job,和Step实例提供CRUD操作。

    外部控制器调用JobLauncher启动一个Job,Job调用自己的Step去实现对数据的操作,Step处理完成后,再将处理结果一步步返回给上一层,这就是Batch处理实现的一个简单流程。

Job详解

基本配置

Job的配置有3个必须的属性,name,jobRepository,steps。一个简单的Job配置如下:

<job id="footballJob">
    <step id="playerload"          parent="s1" next="gameLoad"/>
    <step id="gameLoad"            parent="s2" next="playerSummarization"/>
    <step id="playerSummarization" parent="s3"/>
</job>

jobRepository默认引用名称为jobRepository的bean,当然也可以显式地配置:

<job id="footballJob" job-repository="specialRepository">
    <step id="playerload"          parent="s1" next="gameLoad"/>
    <step id="gameLoad"            parent="s3" next="playerSummarization"/>
    <step id="playerSummarization" parent="s3"/>
</job>

Restartable属性

该属性定义Job是否可以被重启,默认为true,在JobExecution执行失败后,可以创建另一个JobExecution来继续上次的执行。但是如果该属性设为false,重新执行该JobInstance将抛出异常。

<job id="footballJob" restartable="false">
    ...
</job>

拦截Job执行

Spring Batch在Job的生命周期中提供了一些钩子方法,可这些钩子方法通过Listener的形式提供。JobListener的接口定义如下:

public interface JobExecutionListener {

    void beforeJob(JobExecution jobExecution);

    void afterJob(JobExecution jobExecution);

}

通过实现JobExecutionListener接口并配置给Job,可以在Job执行前后执行特定的逻辑。例如在执行结束之后,如果失败,发送邮件通知管理人员等。

<job id="footballJob">
    <step id="playerload"          parent="s1" next="gameLoad"/>
    <step id="gameLoad"            parent="s2" next="playerSummarization"/>
    <step id="playerSummarization" parent="s3"/>
    <listeners>
        <listener ref="sampleListener"/>
    </listeners>
</job>

需要注意的是,无论Job是否成功执行,afterJob方法都会执行,Job是否执行成功,可以从JobExecution中获取。

public void afterJob(JobExecution jobExecution){
    if( jobExecution.getStatus() == BatchStatus.COMPLETED ){
        //job success
    }
    else if(jobExecution.getStatus() == BatchStatus.FAILED){
        //job failure
    }
}

Listener的执行顺序:

beforeJob与配置的顺序一样,afterJob与配置的顺序相反。

Listener异常:

Listener的执行过程中如果抛出异常,将导致Job无法继续完成,最终状态为FAILED.因此要合理控制Listener异常对业务的影响。

注解支持:

如果不想使用侵入性强的Listener接口,可以使用@BeforeJob@AfterJob两个注解声明。

Job抽象与继承

通用的Job配置可以抽取出来,作为抽象的Job存在,抽象的Job不允许被实例化:

<job id="baseJob" abstract="true">
    <listeners>
        <listener ref="listenerOne"/>
    <listeners>
</job>

子Job可以通过继续共用这些配置(当然,也可以继承非抽象的Job)。

<job id="job1" parent="baseJob">
    <step id="step1" parent="standaloneStep"/>

    <listeners merge="true">
        <listener ref="listenerTwo"/>
    <listeners>
</job>

其中的merge=”true”表示合并父job和子job的配置,也就是两个Listener都生效,同常规的Spring配置。

Job参数验证

JobParameterValidator组件用于验证JobParameter。通过以下配置为job配置验证器:

<job id="job1" parent="baseJob3">
    <step id="step1" parent="standaloneStep"/>
    <validator ref="paremetersValidator"/>
</job>

属性的Late Binding

在Spring中,可以把Bean配置用到的属性值通过PropertiesPlaceHolderConfiguer把属性从配置中分离出来独立管理,理论上来说,在配置Job的时候也可以使用相同的方式。但是Spring Batch提供了在运行时配置参数值的能力:

<bean:property name="filePath" value="#{jobParameters['filePath']}" />

在启动Job时:

launcher.executeJob("job.xml" , "footjob", new JobParametersBuilder().addDate("day", new Date()))
                              .addString("filePath", "/opt/data/test.xml"));

配置JobRepository

JobRepository为任务框架中的各个组件对象提供CRUD操作,例如JobExecution,StepExecution。 一个配置例子如下:

<job-repository id="jobRepository"
    data-source="dataSource"
    transaction-manager="transactionManager"
    isolation-level-for-create="SERIALIZABLE"
    table-prefix="BATCH_"
    max-varchar-length="1000"/>

事务配置

JobRepository的操作需要事务来保证其完整性以及正确性,这些元数据的完整性对框架来说非常重要。如果没有事务支持,框架的行为将无法正确定义。 create*方法的事务隔离级别单独定义,为了保证同一个JobInstance不会被同时执行两次,默认的隔离级别为SERIALIZABLE,可以被修改:

<job-repository id="jobRepository" isolation-level-for-create="REPEATABLE_READ" />

如果没有使用Batch命名空间或者没有使用Factory Bean,则需要显示配置事务AOP:

<aop:config>
    <aop:advisor
           pointcut="execution(* org.springframework.batch.core..*Repository+.*(..))"/>
    <advice-ref="txAdvice" />
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

表名前缀

默认情况下,Spring Batch需要的表以BATCH作为前缀,不过可以自定义:

<job-repository id="jobRepository" table-prefix="e_batch" />

表前缀可以修改,但是表名和表的列不能被修改。

特殊的Repository

测试环境中,内存级别的数据库十分方便:

<bean id="jobRepository"
  class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

如果使用的数据库类型不在SpringBatch的支持中,可以通过JobRepositoryFactoryBean自定义。

配置JobLauncher

默认提供了一个简单的Launcher:

<bean id="jobLauncher"
      class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
</bean>

JobLauncher的时序图如下:

如果启动的请求来自HTTP,那么等待整个Job完成再返回不是一个好方法,此时需要异步启动Job,时序图如下:

相应的Launcher配置如下:

<bean id="jobLauncher"
      class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
    <property name="taskExecutor">
        <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
    </property>
</bean>

运行Job

有多种方式可以启动一个Job,但是核心都是通过JobLauncher来实现。

  1. 命令行运行

    主要通过CommandLineJobRunner类完成

  2. 从Web容器中运行

    通过Http请求启动任务很常见,时序图如下:

Controller可以是常规的Spring MVC Controller:

@Controller
public class JobLauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

    @RequestMapping("/jobLauncher.html")
    public void handle() throws Exception{
        jobLauncher.run(job, new JobParameters());
    }
} 

使用调度框架运行  可以与其他调度框架一起使用,例如使用Spring的轻量级调用框架Spring Scheduler或者Quartz

元数据的高级用法

除了通过JobRepository对元数据进行CRUD操作外,Spring batch还提供另外的接口用于访问元数据。  包括: JobExplorer JobOperator。整体结构如下: 

 JobExplorer

该组件提供了只读的查询操作,是JobRepository的只读版本,接口定义如下

public interface JobExplorer {

    List<JobInstance> getJobInstances(String jobName, int start, int count);

    JobExecution getJobExecution(Long executionId);

    StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId);

    JobInstance getJobInstance(Long instanceId);

    List<JobExecution> getJobExecutions(JobInstance jobInstance);

    Set<JobExecution> findRunningJobExecutions(String jobName);
}

配置一个Bean如下:

<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean" p:dataSource-ref="dataSource" />

如果需要制定表名前缀:

<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean" p:dataSource-ref="dataSource" p:tablePrefix="BATCH_" />

 JobOperator

JobOperator集成了很多接口定义,提供了综合的操作方法。定义如下:

public interface JobOperator {

    List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException;

    List<Long> getJobInstances(String jobName, int start, int count)
          throws NoSuchJobException;

    Set<Long> getRunningExecutions(String jobName) throws NoSuchJobException;

    String getParameters(long executionId) throws NoSuchJobExecutionException;

    Long start(String jobName, String parameters)
          throws NoSuchJobException, JobInstanceAlreadyExistsException;

    Long restart(long executionId)
          throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
                  NoSuchJobException, JobRestartException;

    Long startNextInstance(String jobName)
          throws NoSuchJobException, JobParametersNotFoundException, JobRestartException,
                 JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException;

    boolean stop(long executionId)
          throws NoSuchJobExecutionException, JobExecutionNotRunningException;

    String getSummary(long executionId) throws NoSuchJobExecutionException;

    Map<Long, String> getStepExecutionSummaries(long executionId)
          throws NoSuchJobExecutionException;

    Set<String> getJobNames();

}

配置:

<bean id="jobOperator" class="org.spr...SimpleJobOperator">
    <property name="jobExplorer">
        <bean class="org.spr...JobExplorerFactoryBean">
            <property name="dataSource" ref="dataSource" />
        </bean>
    </property>
    <property name="jobRepository" ref="jobRepository" />
    <property name="jobRegistry" ref="jobRegistry" />
    <property name="jobLauncher" ref="jobLauncher" />
</bean>

其中的startNextInstance方法将使用当前Job的JobParameter,经过JobParametersIncrementer处理之后的参数启动一个JobInstance。

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

下面是一个简单实现:

public class SampleIncrementer implements JobParametersIncrementer {

    public JobParameters getNext(JobParameters parameters) {
        if (parameters==null || parameters.isEmpty()) {
            return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
        }
        long id = parameters.getLong("run.id",1L) + 1;
        return new JobParametersBuilder().addLong("run.id", id).toJobParameters();
    }
}

为job配置incrementer:

<job id="footballJob" incrementer="sampleIncrementer">
    ...
</job>

Step详解

面向chunk处理

一个简单的Step配置如下:

<job id="sampleJob" job-repository="jobRepository">
    <step id="step1">
        <tasklet transaction-manager="transactionManager">
            <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
        </tasklet>
    </step>
</job>

1 抽象Step与继承

可以在抽象Step中封装通用逻辑,然后在具体的Step中实现个性化的逻辑:

<step id="parentStep">
    <tasklet allow-start-if-complete="true">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>

<step id="concreteStep1" parent="parentStep">
    <tasklet start-limit="5">
        <chunk processor="itemProcessor" commit-interval="5"/>
    </tasklet>
</step>

2 commit interval

Step在开始处理的时候启动一个事务,通过指定的Spring PlatformTransactionManager周期性地提交Item的写操作,通过interval来指定批次大小:

<job id="sampleJob">
    <step id="step1">
        <tasklet>
            <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
        </tasklet>
    </step>
</job>

3 重启

启动次数限制

可以通过step的start-limit属性来设置Step可以执行的次数:

<step id="step1">
    <tasklet start-limit="2">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>

这个Step只能被执行2次,多次执行将抛出异常。默认情况下,step的start-limit为无穷大,即可以被无限次执行。

重启已经完成的Step

默认情况下,如果一个Step的状态为COMPLETED,那么重新执行该Step时,将被跳过,可以通过配置allow-start-if-complete属性改变这一点,使得已经完成的Step可以重新被执行:

<step id="step1">
    <tasklet allow-start-if-complete="true">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>

4 跳过

对于一些异常情况,我们可能不希望它导致整个任务结束,相反我们希望跳过这些异常的Item,后期再通过日志(通过SkipListener来记录)来特殊处理,这时候可以通过Skip逻辑来实现:

<step id="step1">
   <tasklet>
      <chunk reader="flatFileItemReader" writer="itemWriter"
             commit-interval="10" skip-limit="10">
         <skippable-exception-classes>
            <include class="org.springframework.batch.item.file.FlatFileParseException"/>
         </skippable-exception-classes>
      </chunk>
   </tasklet>
</step>

上述配置中,如果出现的异常在skippable异常列表中,则将被跳过,被跳过的Item数量上限为10,超出限制将抛出异常(导致Step失败)。如果skippable列表很长或者难以配置,可以通过include和exclude来配置:

<step id="step1">
    <tasklet>
        <chunk reader="flatFileItemReader" writer="itemWriter"
               commit-interval="10" skip-limit="10">
            <skippable-exception-classes>
                <include class="java.lang.Exception"/>
                <exclude class="java.io.FileNotFoundException"/>
            </skippable-exception-classes>
        </chunk>
    </tasklet>
</step>

在决定是否跳过时,使用抛出的异常的最近的超类(nearest superclass)来决定。 include和exclude的顺序无关紧要。

5 重试

对于数据库用户名或者密码错误这一类异常,再多的重试连接都没有用。但是对于偶然的数据库死锁或者通信连接异常,通过重试很有可能解决问题。Spring Batch对Step提供了重试支持:

<step id="step1">
   <tasklet>
      <chunk reader="itemReader" writer="itemWriter"
             commit-interval="2" retry-limit="3">
         <retryable-exception-classes>
            <include class="org.springframework.dao.DeadlockLoserDataAccessException"/>
         </retryable-exception-classes>
      </chunk>
   </tasklet>
</step>

各项配置与Skip类似。

6 事务属性与回滚

可以配置一些特定的异常,使得这些异常抛出的时候不回滚事务:

<step id="step1">
   <tasklet>
      <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
      <no-rollback-exception-classes>
         <include class="org.springframework.batch.item.validator.ValidationException"/>
      </no-rollback-exception-classes>
   </tasklet>
</step>

事务隔离级别,传播特定等属性也可以配置:

<step id="step1">
    <tasklet>
        <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
        <transaction-attributes isolation="DEFAULT"
                                propagation="REQUIRED"
                                timeout="30"/>
    </tasklet>
</step>

事务属性配置同常规的Spring事务。

7 在Step中注册ItemStream流

Step在执行过程中,会在适当的时机调用ItemStream的回调函数,并且从ItemStream中获取状态等信息,持久化到Repository。因此这些ItemStream必须注册到Step中,默认情况下,如果ItemReader、Processor、Writer实现了ItemStream,会被自动注册。否则,需要手动注册,这通常实在非直接依赖中使用了ItemStream,例如在Delegate中使用了ItemStream。组合模式下的组件是常见的例子:

<step id="step1">
    <tasklet>
        <chunk reader="itemReader" writer="compositeWriter" commit-interval="2">
            <streams>
                <stream ref="fileItemWriter1"/>
                <stream ref="fileItemWriter2"/>
            </streams>
        </chunk>
    </tasklet>
</step>

<beans:bean id="compositeWriter"
            class="org.springframework.batch.item.support.CompositeItemWriter">
    <beans:property name="delegates">
        <beans:list>
            <beans:ref bean="fileItemWriter1" />
            <beans:ref bean="fileItemWriter2" />
        </beans:list>
    </beans:property>
</beans:bean>

8 拦截Step执行

Step的执行过程中,框架提供了许多钩子用于自定义,主要是以listener形式提供。包括

  • StepExecutionListener

  • ChunkListener

  • ItemReaderListener/ProcessorListener/WriterListener

  • SkipListener

  • RetryListener

这些监听器提供的方法主要时间点是:执行之前、执行之后、异常发生时

<step id="step1">
    <tasklet>
        <chunk reader="reader" writer="writer" commit-interval="10"/>
        <listeners>
            <listener ref="chunkListener"/>
        </listeners>
    </tasklet>
</step>

TaskletStep

批处理(chunk)是一种常见的形式,但是许多场合下不一定能满足要求。此时Tasklet可以派上用场。 Tasklet接口只定义了一个方法execute。TaskletStep将循环调用该方法,直到方法返回RepeatStatus.FINISH或者抛出异常。对Tasklet的调用被封装在一个事务中。 使用如下方法定义一个TaskletStep:

<step id="step1">
    <tasklet ref="myTasklet"/>
</step>

这是,不能再使用chunk子元素。

下面是一个Tasklet实现的例子,删除某个目录下的文件:

public class FileDeletingTasklet implements Tasklet, InitializingBean {

    private Resource directory;

    public RepeatStatus execute(StepContribution contribution,
                                ChunkContext chunkContext) throws Exception {
        File dir = directory.getFile();
        Assert.state(dir.isDirectory());

        File[] files = dir.listFiles();
        for (int i = 0; i < files.length; i++) {
            boolean deleted = files[i].delete();
            if (!deleted) {
                throw new UnexpectedJobExecutionException("Could not delete file " +
                                                          files[i].getPath());
            }
        }
        return RepeatStatus.FINISHED;
    }

    public void setDirectoryResource(Resource directory) {
        this.directory = directory;
    }

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(directory, "directory must be set");
    }
}

如下使用该Tasklet:

<job id="taskletJob">
    <step id="deleteFilesInDir">
       <tasklet ref="fileDeletingTasklet"/>
    </step>
</job>

<beans:bean id="fileDeletingTasklet"
            class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
    <beans:property name="directoryResource">
        <beans:bean id="directory"
                    class="org.springframework.core.io.FileSystemResource">
            <beans:constructor-arg value="target/test-outputs/test-dir" />
        </beans:bean>
    </beans:property>
</beans:bean>

TaskletAdapter提供了复用现有服务的能力:

<bean id="myTasklet" class="o.s.b.core.step.tasklet.MethodInvokingTaskletAdapter">
    <property name="targetObject">
        <bean class="org.mycompany.FooDao"/>
    </property>
    <property name="targetMethod" value="updateFoo" />
</bean>

控制Step执行流程

Step条件用于决定该Step完成之后,下一步如何执行。条件分值是根据Step执行结束之后返回的ExistCode来决定的。

next

<job id="job">
    <step id="stepA" parent="s1">
        <next on="*" to="stepB" />
        <next on="FAILED" to="stepC" />
    </step>
    <step id="stepB" parent="s2" next="stepC" />
    <step id="stepC" parent="s3" />
</job>

其中next的on属性支持通配符

  • *: 匹配0或多个字符

  • ?: 匹配精确一个字符

这里的on是指step的ExitStatus。区别于JobExecution和StepExecution的属性BatchStatus,ExitStatus用于表示一个Step执行完成之后处于什么样的状态,而BatchStatus表示Job或Step的执行状态。ExitStatus默认情况下等于Step的BatchStatus.但是可以自定义:

<step id="step1" parent="s1">
    <end on="FAILED" />
    <next on="COMPLETED WITH SKIPS" to="errorPrint1" />
    <next on="*" to="step2" />
</step>

其中ExitStatus “COMPLETED WITH SKIPS”来自以下Listener:

public class SkipCheckingListener extends StepExecutionListenerSupport {
    public ExitStatus afterStep(StepExecution stepExecution) {
        String exitCode = stepExecution.getExitStatus().getExitCode();
        if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
              stepExecution.getSkipCount() > 0) {
            return new ExitStatus("COMPLETED WITH SKIPS");
        }
        else {
            return null;
        }
    }
}

注意next元素和next属性不能同时使用。

next属性除了指向另外一个step外,还可以指向一个Decider

<job id="job">
    <step id="step1" parent="s1" next="decision" />

    <decision id="decision" decider="decider">
        <next on="FAILED" to="step2" />
        <next on="COMPLETED" to="step3" />
    </decision>

    <step id="step2" parent="s2" next="step3"/>
    <step id="step3" parent="s3" />
</job>

<beans:bean id="decider" class="com.MyDecider"/>

MyDecider定义如下:

public class MyDecider implements JobExecutionDecider {
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        if (someCondition) {
            return "FAILED";
        }
        else {
            return "COMPLETED";
        }
    }
}

Decider用于对自定义条件分支。通常情况下,next根据step的返回结果决定下一步如何执行。但是如果使用Decider,那么step执行完成之后,将通过当前的执行上下文,根据Decider返回的状态,决定下一步如何执行。也就是说,默认情况下有一个默认的Decider,这个Decider简单地根据Step返回结果决定下一步,伪代码如下:

public FlowExecutionStatus decide(... , ...) {
    return stepExecution.getExitStatus();
}

flow

flow用于包装一组step,可以被重用。例如:

<job id="job">
    <flow id="job1.flow1" parent="flow1" next="step3"/>
    <step id="step3" parent="s3"/>
</job>

<flow id="flow1">
    <step id="step1" parent="s1" next="step2"/>
    <step id="step2" parent="s2"/>
</flow>

上述flow定义组合了2个step,可以被多个job共用。

另外,可以通过split将多个flow并行执行:

<split id="split1" next="step4">
    <flow>
        <step id="step1" parent="s1" next="step2"/>
        <step id="step2" parent="s2"/>
    </flow>
    <flow>
        <step id="step3" parent="s3"/>
    </flow>
</split>
<step id="step4" parent="s4"/>

上述两个flow可以并发执行,当两个都完成,也就是split执行完成之后,按照顺序执行下一个step step4.

JobStep

JobSte将其他Job作为一个Step来执行:

<job id="jobStepJob" restartable="true">
   <step id="jobStepJob.step1" next="step2">
      <job ref="job" job-launcher="jobLauncher"
          job-parameters-extractor="jobParametersExtractor"/>
   </step>
   <step id=step2> ... <step>
</job>

<job id="job" restartable="true">...</job>

<bean id="jobParametersExtractor" class="org.spr...DefaultJobParametersExtractor">
   <property name="keys" value="input.file"/>
</bean>

JobStep会创建一个新的Job Execution。 job-parameters-extractor用于将当前的JobParameter转化为新启Job的JobParameter。  区别于flow,JobStep作为一个独立的step(step内启动新的job execution),而flow只是简单地执行flow中定义的step,整个flow不会作为一个单独的step运行。 

在Step中结束Job

在step的执行过程中,可以根据执行结果中断整个job,Spring Batch提供了fail、end、stop三种元素用于在step中中断job。  通常情况下,job的step配置中都有一个没有下一步的step (have had at least one final Step with no transitions),该step决定Job的最终状态:

  • 如果step的返回状态为FAILED,则BatchStatus和ExitStatus都为FAILED

  • 否则,两者的状态均为COMPLETED

1 end

end元素将使得Jb最终BatchStatus为COMPLETED(不能被重启),如果job通过end元素结束,则ExitStatus默认为COMPLETED,当然可以自定义ExitStatus。

<step id="step1" parent="s1" next="step2">

<step id="step2" parent="s2">
    <end on="FAILED"/>
    <next on="*" to="step3"/>
</step>

<step id="step3" parent="s3">

2 fail

fail元素引导Job以FAILED状态结束,另外可以自定义ExitStatus。以该方式结束的Job可以被重启。

<step id="step1" parent="s1" next="step2">

<step id="step2" parent="s2">
    <fail on="FAILED" exit-code="EARLY TERMINATION"/>
    <next on="*" to="step3"/>
</step>

<step id="step3" parent="s3">

3 stop

stop元素使得Job以STOPPED状态结束,该状态的Job可以被重启(针对STOPPED状态采取一些动作之后),但是必须指定下一次重启的step:

<step id="step1" parent="s1">
    <stop on="COMPLETED" restart="step2"/>
</step>

<step id="step2" parent="s2"/>

Late Binding与Step、Job scope

对于Job和Step的属性配置,可以使用Spring常规的占位符,将属性配置独立到properties文件中,或者在运行时通过-D参数传入。例如:

<bean id="flatFileItemReader"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="${input.file.name}" />
</bean>

然后在运行时通过-D传入参数:

-Dinput.file.name="file://file.txt"

或者通过properties文件:

input.file.name=file://file.txt

但是在Spring Batch中,更方便的方式是通过JobParameter传入。为了实现该功能,Spring Batch通过自定义scope来达到Job和Step属性的后期绑定(Late Binding)。

<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobParameters['input.file.name']}" />
</bean>

另外,ExecutionContext中的属性也都可以用于组件的属性配置。

<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobExecutionContext['input.file.name']}" />
</bean>


<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{stepExecutionContext['input.file.name']}" />
</bean>

Step Scope

注意到上述的配置中,bean都设置了scope属性,这是必须的。为了在step运行时才绑定参数,这些bean只有在Step启动时才会被实例化。由于step或者job scope不是spring默认的scope,因此需要显示配置,有2中方式可以达到该目的:

使用batch命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="...">
<batch:job .../>
...
</beans>

显式配置scope

<bean class="org.springframework.batch.core.scope.StepScope" />

两者只能选其一。

Job Scope

类似于step scope,job scope确保一个执行中的job只有一个bean实例。同时也使得可以从JobParameter或者jobExecutionContext中后期绑定参数。

<bean id="..." class="..." scope="job">
    <property name="name" value="#{jobParameters[input]}" />
</bean>
<bean id="..." class="..." scope="job">
    // spring EL
    <property name="name" value="#{jobExecutionContext['input.name']}.txt" />
</bean>

对于上述bean,如果在多个job配置中引用该bean,那么job scope确保对于这些Job的任何JobInstance,都只有一个唯一的bean。

job scope一样可以通过使用batch命名空间或者显式声明:

<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:batch="http://www.springframework.org/schema/batch"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="...">

          <batch:job .../>
          ...
</beans>

<bean class="org.springframework.batch.core.scope.JobScope" />

Step执行过程:

      Spring Batch在其最常见的用法是面向块的处理方式。面向块的处理是指一次读取一个数据,并在事务边界内创建将被写出的chunk。 从DB或是文件中取出数据的时候,read()操作每次只读取一条记录,之后将读取的这条数据传递给processor(item)处理,框架将重复做这两步操作,直到读取记录的件数达到batch配置信息中commin-interval的设定值的时候,就会调用一次write操作。然后再重复上图的处理,直到处理完所有的数据。当这个Step的工作完成以后,或是跳到下一个Step,或者结束处理。

      这就是一个SpringBatch的step基本工作流程。

Job-Step-Tasklet-Chunk

一个 job 可以包含 0到多个 step; 一个 step 可以有 0或1 个 tasklet; 一个 tasklet 可以有 0或1 个 chunk。

<job id="JobName" xmlns="http://www.springframework.org/schema/batch">
        <step id="StepName1" next="StepName2">
            <tasklet>
                <chunk reader="someReader" processor="someProcessor" writer="someWriter" commit-interval="5" />
            </tasklet>
        </step>
        <step id="StepName2">
            <tasklet ref="someTasklet" />
        </step>
        <listeners>
            <listener ref="someListener"/>
        </listeners>

    </job>

若step例的tasklet为实现TaskLet接口的实例,则这个step只会调用一次tasklet。

Spring Batch的监听器

Spring 支持如下监听器。

监听器

说明

JobExecutionListener

在 Job 开始之前(重写beforeJob方法)和之后(afterJob)触发

StepExecutionListener

在 Step 开始之前(beforeStep)和之后(afterStep)触发

ChunkListener

在 Chunk 开始之前(beforeChunk),之后(afterChunk)和错误后(afterChunkError)触发

ItemReadListener

在 Read 开始之前(beforeRead),之后(afterRead)和错误后(onReadError)触发

ItemProcessListener

在 Read 开始之前(beforeProcess),之后(afterProcess)和错误后(onProcessError)触发

ItemWriteListener

在 Read 开始之前(beforeWrite),之后(afterWrite)和错误后(onWriteError)触发

SkipListener

在 Read 开始之前(beforeWrite),之后(afterWrite)和错误后(onWriteError)触发

例程源码地址

Reference

Spring Batch官方文档

https://docs.spring.io/spring-batch/2.2.x/reference/html/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值