進階中繼資料使用

到目前為止,已討論過 JobLauncherJobRepository 介面。它們共同代表了 Job 的簡單啟動和批次網域物件的基本 CRUD 操作

Job Repository
圖 1. Job Repository

JobLauncher 使用 JobRepository 建立新的 JobExecution 物件並執行它們。JobStep 實作稍後會在 Job 執行期間使用相同的 JobRepository 進行相同執行的基本更新。基本操作足以應付簡單的場景。但是,在具有數百個批次 Job 和複雜排程需求的大型批次環境中,需要更進階的中繼資料存取

Job Repository Advanced
圖 2. 進階 Job Repository 存取

即將章節中討論的 JobExplorerJobOperator 介面,增加了查詢和控制中繼資料的其他功能。

查詢 Repository

在任何進階功能之前,最基本的需求是能夠查詢 Repository 以取得現有的執行。此功能由 JobExplorer 介面提供

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);
}

從其方法簽名顯而易見,JobExplorerJobRepository 的唯讀版本,並且與 JobRepository 一樣,可以使用 factory bean 輕鬆設定。

  • Java

  • XML

以下範例顯示如何在 Java 中設定 JobExplorer

Java 設定
...
// This would reside in your DefaultBatchConfiguration extension
@Bean
public JobExplorer jobExplorer() throws Exception {
	JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
	factoryBean.setDataSource(this.dataSource);
	return factoryBean.getObject();
}
...

以下範例顯示如何在 XML 中設定 JobExplorer

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

在本章稍早,我們注意到您可以修改 JobRepository 的資料表前綴,以允許不同的版本或 Schema。由於 JobExplorer 使用相同的資料表,因此它也需要能夠設定前綴。

  • Java

  • XML

以下範例顯示如何在 Java 中設定 JobExplorer 的資料表前綴

Java 設定
...
// This would reside in your DefaultBatchConfiguration extension
@Bean
public JobExplorer jobExplorer() throws Exception {
	JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
	factoryBean.setDataSource(this.dataSource);
	factoryBean.setTablePrefix("SYSTEM.");
	return factoryBean.getObject();
}
...

以下範例顯示如何在 XML 中設定 JobExplorer 的資料表前綴

XML 設定
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
		p:tablePrefix="SYSTEM."/>

JobRegistry

JobRegistry(及其父介面 JobLocator)不是強制性的,但如果您想追蹤內容中可用的 Job,它可能會很有用。當 Job 在其他地方(例如,在子內容中)建立時,它對於在應用程式內容中集中收集 Job 也很有用。您也可以使用自訂 JobRegistry 實作來操作已註冊 Job 的名稱和其他屬性。框架僅提供一個實作,它基於從 Job 名稱到 Job 實例的簡單映射。

  • Java

  • XML

當使用 @EnableBatchProcessing 時,會為您提供 JobRegistry。以下範例顯示如何設定您自己的 JobRegistry

...
// This is already provided via the @EnableBatchProcessing but can be customized via
// overriding the bean in the DefaultBatchConfiguration
@Override
@Bean
public JobRegistry jobRegistry() throws Exception {
	return new MapJobRegistry();
}
...

以下範例顯示如何為 XML 中定義的 Job 包含 JobRegistry

<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

您可以使用以下其中一種方式來填入 JobRegistry:使用 bean 後處理器,或使用 smart initializing singleton,或使用 registrar lifecycle component。接下來的章節將說明這些機制。

JobRegistryBeanPostProcessor

這是一個 bean 後處理器,可以在建立所有 Job 時註冊它們。

  • Java

  • XML

以下範例顯示如何為 Java 中定義的 Job 包含 JobRegistryBeanPostProcessor

Java 設定
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
    JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
    postProcessor.setJobRegistry(jobRegistry);
    return postProcessor;
}

以下範例顯示如何為 XML 中定義的 Job 包含 JobRegistryBeanPostProcessor

XML 設定
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor">
    <property name="jobRegistry" ref="jobRegistry"/>
</bean>

雖然不是絕對必要,但範例中的後處理器已給定一個 id,以便它可以包含在子內容中(例如,作為父 bean 定義),並使在那裡建立的所有 Job 也自動註冊。

從 5.1 版開始,@EnableBatchProcessing 注釋會自動在應用程式內容中註冊 jobRegistryBeanPostProcessor bean。

JobRegistrySmartInitializingSingleton

這是一個 SmartInitializingSingleton,可在 Job 註冊表中註冊所有 singleton Job。

  • Java

  • XML

以下範例顯示如何在 Java 中定義 JobRegistrySmartInitializingSingleton

Java 設定
@Bean
public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) {
    return new JobRegistrySmartInitializingSingleton(jobRegistry);
}

以下範例顯示如何在 XML 中定義 JobRegistrySmartInitializingSingleton

XML 設定
<bean class="org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton">
    <property name="jobRegistry" ref="jobRegistry" />
</bean>

AutomaticJobRegistrar

這是一個生命週期元件,可建立子內容並從這些內容中註冊 Job(在建立時)。這樣做的一個優點是,雖然子內容中的 Job 名稱仍然必須在註冊表中全域唯一,但它們的依賴項可以具有「自然」名稱。因此,例如,您可以建立一組 XML 設定檔,每個檔案都只有一個 Job,但都具有相同 bean 名稱(例如 reader)的 ItemReader 的不同定義。如果所有這些檔案都匯入到相同的內容中,則 reader 定義會衝突並覆蓋彼此,但使用自動註冊器,則可以避免這種情況。這使得整合從應用程式的不同模組貢獻的 Job 變得更容易。

  • Java

  • XML

以下範例顯示如何為 Java 中定義的 Job 包含 AutomaticJobRegistrar

Java 設定
@Bean
public AutomaticJobRegistrar registrar() {

    AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
    registrar.setJobLoader(jobLoader());
    registrar.setApplicationContextFactories(applicationContextFactories());
    registrar.afterPropertiesSet();
    return registrar;

}

以下範例顯示如何為 XML 中定義的 Job 包含 AutomaticJobRegistrar

XML 設定
<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>

註冊器具有兩個強制屬性:ApplicationContextFactory 陣列(從前面範例中的方便 factory bean 建立)和 JobLoaderJobLoader 負責管理子內容的生命週期並在 JobRegistry 中註冊 Job。

ApplicationContextFactory 負責建立子內容。最常見的用法是(如前面的範例中)使用 ClassPathXmlApplicationContextFactory。此 factory 的其中一個功能是,預設情況下,它會將一些設定從父內容複製到子內容。因此,例如,您不需要在子內容中重新定義 PropertyPlaceholderConfigurer 或 AOP 設定,前提是它應該與父內容相同。

您可以將 AutomaticJobRegistrarJobRegistryBeanPostProcessor 結合使用(只要您也使用 DefaultJobLoader)。例如,如果主要父內容以及子位置中都定義了 Job,則可能需要這樣做。

JobOperator

如先前所討論,JobRepository 提供對中繼資料的 CRUD 操作,而 JobExplorer 提供對中繼資料的唯讀操作。但是,當它們一起使用以執行常見的監控任務(例如停止、重新啟動或摘要 Job)時,這些操作最有用,批次操作員通常會這樣做。Spring Batch 在 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();

}

先前的操作代表來自許多不同介面的方法,例如 JobLauncherJobRepositoryJobExplorerJobRegistry。因此,提供的 JobOperator 實作 (SimpleJobOperator) 有許多依賴項。

  • Java

  • XML

以下範例顯示 Java 中 SimpleJobOperator 的典型 bean 定義

 /**
  * All injected dependencies for this bean are provided by the @EnableBatchProcessing
  * infrastructure out of the box.
  */
 @Bean
 public SimpleJobOperator jobOperator(JobExplorer jobExplorer,
                                JobRepository jobRepository,
                                JobRegistry jobRegistry,
                                JobLauncher jobLauncher) {

	SimpleJobOperator jobOperator = new SimpleJobOperator();
	jobOperator.setJobExplorer(jobExplorer);
	jobOperator.setJobRepository(jobRepository);
	jobOperator.setJobRegistry(jobRegistry);
	jobOperator.setJobLauncher(jobLauncher);

	return jobOperator;
 }

以下範例顯示 XML 中 SimpleJobOperator 的典型 bean 定義

<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>

從 5.0 版開始,@EnableBatchProcessing 注釋會自動在應用程式內容中註冊 job operator bean。

如果您在 job repository 上設定資料表前綴,請不要忘記在 job explorer 上也設定它。

JobParametersIncrementer

JobOperator 上的大多數方法都是不言自明的,您可以在 介面的 Javadoc 中找到更詳細的說明。但是,startNextInstance 方法值得注意。此方法始終啟動 Job 的新實例。如果 JobExecution 中存在嚴重問題,並且需要從頭開始重新啟動 Job,這可能非常有用。與 JobLauncher(需要新的 JobParameters 物件來觸發新的 JobInstance)不同,如果參數與任何先前的參數集不同,則 startNextInstance 方法會使用繫結到 JobJobParametersIncrementer 強制 Job 成為新實例

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

JobParametersIncrementer 的合約是,給定一個 JobParameters 物件,它會透過遞增它可能包含的任何必要值來傳回「下一個」JobParameters 物件。此策略很有用,因為框架無法知道對 JobParameters 的哪些變更使其成為「下一個」實例。例如,如果 JobParameters 中唯一的 value 是日期,並且應該建立下一個實例,那麼該 value 應該遞增一天還是 一週(如果 Job 是每週的,例如)?對於任何有助於識別 Job 的數值也是如此,如下例所示

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();
    }
}

在此範例中,鍵為 run.id 的 value 用於區分 JobInstances。如果傳入的 JobParameters 為 null,則可以假設 Job 從未執行過,因此可以傳回其初始狀態。但是,如果不是,則會取得舊 value,遞增 1,然後傳回。

  • Java

  • XML

對於在 Java 中定義的 Job,您可以透過 builders 中提供的 incrementer 方法將 incrementer 與 Job 關聯,如下所示

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
    				 .incrementer(sampleIncrementer())
    				 ...
                     .build();
}

對於在 XML 中定義的 Job,您可以透過命名空間中的 incrementer 屬性將 incrementer 與 Job 關聯,如下所示

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

停止 Job

JobOperator 最常見的用例之一是優雅地停止 Job

Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");
jobOperator.stop(executions.iterator().next());

關閉不是立即的,因為沒有辦法強制立即關閉,特別是如果執行目前位於框架無法控制的開發人員程式碼中,例如業務服務。但是,一旦控制權返回到框架,它就會將目前 StepExecution 的狀態設定為 BatchStatus.STOPPED,儲存它,並在完成之前對 JobExecution 執行相同的操作。

中止 Job

FAILED 的 Job 執行可以重新啟動(如果 Job 是可重新啟動的)。狀態為 ABANDONED 的 Job 執行無法由框架重新啟動。ABANDONED 狀態也用於 step 執行中,以將它們標記為在重新啟動的 Job 執行中可跳過的。如果 Job 正在執行並遇到在先前失敗的 Job 執行中標記為 ABANDONED 的 step,它會繼續執行下一個 step(由 Job 流程定義和 step 執行結束狀態決定)。

如果程序死掉 (kill -9 或伺服器故障),Job 當然不會執行,但 JobRepository 無法知道,因為在程序死掉之前沒有人告訴它。您必須手動告訴它您知道執行失敗或應視為中止(將其狀態變更為 FAILEDABANDONED)。這是一個業務決策,沒有辦法自動化。僅當它是可重新啟動的並且您知道重新啟動資料有效時,才將狀態變更為 FAILED