Spring batch整體的架構設計使用如下關係圖來進行表示:
雖然Job對象看上去像是對於多個Step的一個簡單容器,但是開發者必須要注意許多配置項。此外,Job的運行以及Job運行過程中元數據如何被保存也是需要考慮的。本章將會介紹Job在運行時所需要注意的各種配置項。
1.1 Configuring a Job
Job接口 的實現有多個,但是在配置上命名空間存在着不同。必須依賴的只有三項:名稱 name,JobRespository 和 Step的列表:
<job id="footballJob">
<step id="playerload" parent="s1" next="gameLoad"/>
<step id="gameLoad" parent="s2" next="playerSummarization"/>
<step id="playerSummarization" parent="s3"/>
</job>
在這個例子中使用了父類的bean定義來創建step,更多描述step配置的信息可以參考step configuration這一節。XML命名空間默認會使用id爲'jobRepository'的引用來作爲repository的定義。然而可以向如下顯式的覆蓋:
<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>
1.1.1 Restartablity
執行批處理任務的一個關鍵問題是要考慮job被重啓後的行爲。如果一個 JobExecution 已經存在一個特定的 JobInstance,那麼這個job啓動時可以認爲是“重啓”。 理想情況下,所有任務都能夠在他們中止的地方啓動,但是有許多場景這是不可能的。在這種場景中就要有開發者來決定創建一個新的 JobInstance ,Spring對此也提供了一些幫助。如果job不需要重啓,而是總是作爲新的 JobInstance 來運行,那麼可重啓屬性可以設置爲'false':
<job id="footballJob" restartable="false">
...
</job>
設置重啓屬性restartable爲‘false’表示‘這個job不支持再次啓動’,重啓一個不可重啓的job會拋出JobRestartExceptio的異常:
Job job = new SimpleJob();
job.setRestartable(false);
JobParameters jobParameters = new JobParameters();
JobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters);
jobRepository.saveOrUpdate(firstExecution);
try {
jobRepository.createJobExecution(job, jobParameters);
fail();
} catch (JobRestartException e) {
//預計拋出JobRestartException異常
}
這個JUnit代碼展示了創建一個不可重啓的Job後,第一次能夠創建 JobExecution ,第二次再創建相同的JobExcution會拋出一個 JobRestartException。
1.1.2 Intercepting Job Execution
在job執行過程中,自定義代碼能夠在生命週期中通過事件通知執行會是很有用的。SimpleJob能夠在適當的時機調用JobListener:
public interface JobExecutionListener {
void beforeJob(JobExecution jobExecution);
void afterJob(JobExecution jobExecution);
}
<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,都可以從 JobExecution 中獲取運行結果後,根據結果來進行不同的處理:
public void afterJob(JobExecution jobExecution){
if( jobExecution.getStatus() == BatchStatus.COMPLETED ){
//job執行成功 }
else if(jobExecution.getStatus() == BatchStatus.FAILED){
//job執行失敗 }
}
- @BeforeJob
- @AfterJob
1.1.3 Inheriting from a parent Job
下面的例子中,“baseJob”是一個抽象的job定義,只定義了一個監聽器列表。名爲“job1”的job是一個具體定義,它繼承了“baseJob"的監聽器,並且與自己的監聽器合併,最終生成的job帶有兩個監聽器,以及一個名爲”step1“的step。
<job id="baseJob" abstract="true">
<listeners>
<listener ref="listenerOne"/>
</listeners>
</job>
<job id="job1" parent="baseJob">
<step id="step1" parent="standaloneStep"/>
<listeners merge="true">
<listener ref="listenerTwo"/>
</listeners>
</job>
1.1.4 JobParametersValidator
<job id="job1" parent="baseJob3">
<step id="step1" parent="standaloneStep"/>
<validator ref="paremetersValidator"/>
</job>
1.2 Java Config
下, @EnableBatchProcessing 提供了構建批處理任務的基本配置。在這個基本的配置中,除了創建了一個 StepScope 的實例,還可以將一系列可用的bean進行自動裝配:
- JobRepository bean 名稱 "jobRepository"
- JobLauncher bean名稱"jobLauncher"
- JobRegistry bean名稱"jobRegistry"
- PlatformTransactionManager bean名稱 "transactionManager"
- JobBuilderFactory bean名稱"jobBuilders"
- StepBuilderFactory bean名稱"stepBuilders"
注意 只有一個配置類需要有@ enablebatchprocessing註釋。只要有一個類添加了這個註釋,則以上所有的bean都是可以使用的。
在基本配置中,用戶可以使用所提供的builder factory來配置一個job。下面的例子是通過 JobBuilderFactory 和
StepBuilderFactory 配置的兩個step job 。
@Configuration
@EnableBatchProcessing
@Import(DataSourceCnfiguration.class)
public class AppConfig {
@Autowired
private JobBuilderFactory jobs;
@Autowired
private StepBuilderFactory steps;
@Bean
public Job job() {
return jobs.get("myJob").start(step1()).next(step2()).build();
}
@Bean
protected Step step1(ItemReader<Person> reader, ItemProcessor<Person, Person> processor, ItemWriter<Person> writer) {
return steps.get("step1")
.<Person, Person> chunk(10)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
protected Step step2(Tasklet tasklet) {
return steps.get("step2")
.tasklet(tasklet)
.build();
}
}
1.3 Configuring a JobRepository
<job-repository id="jobRepository"
data-source="dataSource"
transaction-manager="transactionManager"
isolation-level-for-create="SERIALIZABLE"
table-prefix="BATCH_"
max-varchar-length="1000"/>
1.3.1 JobRepository 的事物配置
<job-repository id="jobRepository" isolation-level-for-create="REPEATABLE_READ" />
<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>
1.3.2 修改 Table 前綴
BATCH_STEP_EXECUTION 就是兩個例子。但是,有一些潛在的原因可能需要修改這個前綴。例如schema的名字需要被預置到表名中,或是不止一組的元數據表需要放在同一個schema中,那麼表前綴就需要改變:
<job-repository id="jobRepository" table-prefix="SYSTEM.TEST_" />
注意:表名前綴是可配置的,表名和列名是不可配置的。
1.3.3 In-Memory Repository
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
但是也需要定義一個事務管理器,因爲倉庫需要回滾語義,也因爲商業邏輯要求事務性(例如RDBMS訪問)。經過測試許多人覺得 ResourcelessTransactionManager 是很有用的。
1.3.4 Non-standard Database Types in a Repository
<bean id="jobRepository" class="org...JobRepositoryFactoryBean">
<property name="databaseType" value="db2"/>
<property name="dataSource" ref="dataSource"/>
</bean>
1.4 Configuring a JobLauncher
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<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>
1.5 Running a Job
1.5.1 在 Web Container 內部運行 Jobs
@Controller
public class JobLauncherController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job job;
@RequestMapping("/jobLauncher.html")
public void handle() throws Exception{
jobLauncher.run(job, new JobParameters());
}
}
1.6 Meta-Data 高級用法
1.6.1 Querying the Repository
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 id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
p:dataSource-ref="dataSource" />
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
p:dataSource-ref="dataSource" p:tablePrefix="BATCH_" />
1.6.2 JobRegistry
<bean id="jobRegistry" class="org.spr...MapJobRegistry" />
JobRegistryBeanPostProcessor
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
並不一定要像例子中給post處理器一個id,但是使用id可以在子context中(比如作爲作爲父 bean 定義)也使用post處理器,這樣所有的job在創建時都會自動註冊進JobRegistry。
AutomaticJobRegistrar
<bean class="org.spr...AutomaticJobRegistrar">
<property name="applicationContextFactories">
<bean class="org.spr...ClasspathXmlApplicationContextsFactoryBean">
<property name="resources" value="classpath*:/config/job*.xml" />
</bean>
</property>
<property name="jobLoader">
<bean class="org.spr...DefaultJobLoader">
<property name="jobRegistry" ref="jobRegistry" />
</bean>
</property>
</bean>
ClassPathXmlApplicationContextFactory。這個工廠類的一個特性是默認情況下他會複製父上下文的一些配置到子上下文。因此如果不變的情況下不需要重新定義子上下文中的 PropertyPlaceholderConfigurer 和AOP配置。
在必要情況下,AutomaticJobRegistrar 可以和 JobRegistyBeanPostProcessor 一起使用。例如,job有可能既定義在父上下文中也定義在子上下文中的情況。
1.6.3 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>
1.6.4 JobParametersIncrementer
startNextInstance方法卻有些無所是處。這個方法通常用於啓動Job的一個新的實例。但如果 JobExecution 存在若干嚴重的問題,同時該Job 需要從頭重新啓動,那麼這時候這個方法就相當有用了。不像JobLauncher ,啓動新的任務時如果參數不同於任何以往的參數集,這就要求一個新的 JobParameters 對象來觸發新的 JobInstance,startNextInstance 方法將使用當前的JobParametersIncrementer綁定到這個任務,並強制其生成新的實例:
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 id="footballJob" incrementer="sampleIncrementer">
...
</job>
1.6.5 Stopping a Job
Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");
jobOperator.stop(executions.iterator().next());