高度なメタデータの使用

これまで、JobLauncher および JobRepository インターフェースの両方について説明してきました。これらは一緒に、ジョブの単純な起動と、バッチドメインオブジェクトの基本的な CRUD 操作を表します。

Job Repository
図 1: ジョブリポジトリ

JobLauncher は、JobRepository を使用して新しい JobExecution オブジェクトを作成し、実行します。Job および Step の実装は、後で Job の実行中に同じ実行の基本的な更新に同じ JobRepository を使用します。単純なシナリオでは、基本的な操作で十分です。ただし、数百のバッチジョブと複雑なスケジューリング要件がある大規模なバッチ環境では、メタデータへのより高度なアクセスが必要です。

Job Repository Advanced
図 2: 高度なジョブリポジトリアクセス

以降のセクションで説明する JobExplorer および JobOperator インターフェースは、メタデータのクエリと制御のための追加機能を追加します。

リポジトリのクエリ

高度な機能を使用する前の最も基本的なニーズは、既存の実行についてリポジトリをクエリする機能です。この機能は、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);
}

そのメソッドシグネチャーから明らかなように、JobExplorer は JobRepository の読み取り専用バージョンであり、JobRepository と同様に、ファクトリ 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 のテーブルプレフィックスを変更できることに注意しました。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) は必須ではありませんが、コンテキストで使用可能なジョブを追跡したい場合に役立ちます。また、ジョブが別の場所 (子コンテキストなど) で作成された場合に、アプリケーションコンテキストで一元的にジョブを収集する場合にも役立ちます。カスタム JobRegistry 実装を使用して、登録されているジョブの名前やその他のプロパティを操作することもできます。フレームワークによって提供される実装は 1 つだけで、これはジョブ名からジョブインスタンスへの単純なマップに基づいています。

  • 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 で定義されたジョブに JobRegistry を含める方法を示しています。

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

JobRegistry は、Bean ポストプロセッサーを使用するか、スマート初期化シングルトンを使用するか、レジストラーライフサイクルコンポーネントを使用するかのいずれかの方法で設定できます。次のセクションでは、これらのメカニズムについて説明します。

JobRegistryBeanPostProcessor

これは、作成時にすべてのジョブを登録できる Bean ポストプロセッサーです。

  • Java

  • XML

次の例は、Java で定義されたジョブに JobRegistryBeanPostProcessor を含める方法を示しています。

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

次の例は、XML で定義されたジョブに JobRegistryBeanPostProcessor を含める方法を示しています。

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

厳密には必要ではありませんが、この例のポストプロセッサーには id が与えられているため、子コンテキストに含めることができ (たとえば、親 Bean 定義として)、そこで作成されたすべてのジョブも自動的に登録されます。

バージョン 5.1 以降、@EnableBatchProcessing アノテーションはアプリケーションコンテキストに jobRegistryBeanPostProcessor Bean を自動的に登録します。

JobRegistrySmartInitializingSingleton

これは、ジョブレジストリ内のすべてのシングルトンジョブを登録する SmartInitializingSingleton です。

  • Java

  • XML

次の例は、Java で JobRegistrySmartInitializingSingleton を定義する方法を示しています。

Java 構成
@Bean
public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) {
    return new JobRegistrySmartInitializingSingleton(jobRegistry);
}

次の例は、JobRegistrySmartInitializingSingleton を XML で定義する方法を示しています。

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

AutomaticJobRegistrar

これは、子コンテキストを作成し、作成時にそれらのコンテキストからジョブを登録するライフサイクルコンポーネントです。これを行う利点の 1 つは、子コンテキストのジョブ名がレジストリ内でグローバルに一意である必要がある一方で、それらの依存関係に「自然な」名前を付けることができることです。たとえば、それぞれが 1 つのジョブのみを持ち、すべて同じ Bean 名を持つ ItemReader の異なる定義 ( reader など) を持つ一連の XML 構成ファイルを作成できます。これらすべてのファイルが同じコンテキストにインポートされた場合、リーダーの定義が衝突して相互に上書きされますが、自動レジストラーを使用すると、これが回避されます。これにより、アプリケーションの個別のモジュールから提供されたジョブを簡単に統合できます。

  • Java

  • XML

次の例は、Java で定義されたジョブに AutomaticJobRegistrar を含める方法を示しています。

Java 構成
@Bean
public AutomaticJobRegistrar registrar() {

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

}

次の例は、XML で定義されたジョブに 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>

レジストラには 2 つの必須プロパティがあります。ApplicationContextFactory の配列 (前の例では便利なファクトリ Bean から作成されています) と JobLoader です。JobLoader は、子コンテキストのライフサイクルの管理と JobRegistry でのジョブの登録を担当します。

ApplicationContextFactory は、子コンテキストの作成を担当します。最も一般的な使用箇所は (前の例のように) ClassPathXmlApplicationContextFactory を使用することです。このファクトリの機能の 1 つは、デフォルトで、構成の一部を親コンテキストから子コンテキストにコピーすることです。たとえば、子で PropertyPlaceholderConfigurer または AOP 構成を再定義する必要はありませんが、親と同じでなければなりません。

AutomaticJobRegistrar を JobRegistryBeanPostProcessor と組み合わせて使用できます ( DefaultJobLoader も使用する場合)。たとえば、メインの親コンテキストと子ロケーションでジョブが定義されている場合、これが望ましい場合があります。

JobOperator

前述のように、JobRepository はメタデータに対する CRUD 操作を提供し、JobExplorer はメタデータに対する読み取り専用操作を提供します。ただし、これらの操作は、バッチオペレーターによって一般的に行われるように、ジョブの停止、再開、要約などの一般的な監視タスクを実行するために一緒に使用する場合に最も役立ちます。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 アノテーションは、ジョブオペレーター Bean をアプリケーションコンテキストに自動的に登録します。

ジョブリポジトリでテーブルプレフィックスを設定する場合は、ジョブエクスプローラーでも忘れずに設定してください。

JobParametersIncrementer

JobOperator のほとんどのメソッドは自明であり、インターフェースの Javadoc でより詳細な説明を見つけることができます。ただし、startNextInstance メソッドは注目に値します。このメソッドは、常に Job の新しいインスタンスを開始します。これは、JobExecution に深刻な課題があり、Job を最初からやり直す必要がある場合に非常に役立ちます。JobLauncher (新しい JobInstance をトリガーする新しい JobParameters オブジェクトが必要) とは異なり、パラメーターが以前のパラメーターのセットと異なる場合、startNextInstance メソッドは Job に関連付けられた JobParametersIncrementer を使用して、Job を新しいインスタンスに強制します。

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

JobParametersIncrementer の契約は、JobParameters オブジェクトが与えられると、それに含まれる必要な値をインクリメントして「次の」 JobParameters オブジェクトを返すことです。フレームワークは JobParameters へのどの変更が「次の」インスタンスになるかを知る方法がないため、この戦略は役に立ちます。例: JobParameters の唯一の値が日付であり、次のインスタンスを作成する必要がある場合、その値を 1 日または 1 週間ずつ増やす必要がありますか (たとえば、ジョブが毎週の場合)? 次の例に示すように、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 のキーを持つ値を使用して、JobInstances を区別しています。渡された JobParameters が null の場合、Job は以前に実行されたことがないため、初期状態を返すことができると見なすことができます。ただし、そうでない場合は、古い値が取得され、1 つインクリメントされて返されます。

  • Java

  • XML

Java で定義されたジョブの場合、次のように、ビルダーで提供される incrementer メソッドを介してインクリメンタを Job に関連付けることができます。

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

XML で定義されたジョブの場合、次のように、名前空間の incrementer 属性を通じてインクリメンタを Job に関連付けることができます。

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

ジョブの停止

JobOperator の最も一般的な使用例の 1 つは、ジョブを正常に停止することです。

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

特にビジネスサービスなど、フレームワークが制御できない開発者コードで現在実行が行われている場合は、即時シャットダウンを強制する方法がないため、シャットダウンは即時ではありません。ただし、制御がフレームワークに戻されるとすぐに、現在の StepExecution のステータスを BatchStatus.STOPPED に設定して保存し、終了する前に JobExecution に対して同じことを行います。

ジョブの中止

FAILED であるジョブ実行は再開できます (Job が再開可能な場合)。ステータスが ABANDONED のジョブ実行は、フレームワークによって再開できません。ABANDONED ステータスは、ステップ実行でも使用され、再起動されたジョブ実行でスキップ可能としてマークされます。ジョブの実行中に、前回の失敗したジョブ実行で ABANDONED とマークされたステップが検出された場合、ジョブは次のステップに進みます (ジョブフロー定義とステップ実行の終了ステータスによって決定されます)。

プロセスが停止した場合(kill -9 またはサーバー障害)、ジョブはもちろん実行されていませんが、プロセスが停止する前に誰も通知しなかったため、JobRepository は知る方法がありません。実行が失敗したか、中止されたと見なされる必要があることを手動で通知する必要があります(ステータスを FAILED または ABANDONED に変更します)。これはビジネス上の決定であり、自動化する方法はありません。再起動可能であり、再起動データが有効であることがわかっている場合にのみ、状況を FAILED に変更してください。