ステップフローの制御

所有するジョブ内でステップをグループ化できるため、あるステップから別のステップへのジョブの「流れ」を制御できる必要があります。Step の障害は、必ずしも Job の障害を意味するわけではありません。さらに、どの Step を次に実行するかを決定する「成功」の型が複数存在する場合があります。Steps のグループの構成方法によっては、特定のステップがまったく処理されない場合もあります。

フロー定義でのステップ Bean メソッドプロキシ

ステップインスタンスはフロー定義内で一意である必要があります。フロー定義でステップに複数の結果がある場合、ステップの同じインスタンスがフロー定義メソッド (startfrom など) に渡されることが重要です。そうしないと、フローの実行が予期しない動作をする可能性があります。

次の例では、ステップがパラメーターとしてフローまたはジョブの Bean 定義メソッドに挿入されます。この依存性注入スタイルにより、フロー定義内のステップの一意性が保証されます。ただし、@Bean アノテーションが付けられたステップ定義メソッドを呼び出すことによってフローが定義されている場合、Bean メソッドプロキシが無効になっている場合 (すなわち @Configuration(proxyBeanMethods = false))、ステップは一意ではない可能性があります。Bean 間注入スタイルが優先される場合は、Bean メソッドプロキシを有効にする必要があります。

Spring Framework での Bean メソッドプロキシの詳細については、"@Configuration アノテーションの使用" セクションを参照してください。

シーケンシャルフロー

最も単純なフローシナリオは、次の図に示すように、すべてのステップが順番に実行されるジョブです。

Sequential Flow
図 1: シーケンシャルフロー

これは、step で next を使用することで実現できます。

  • Java

  • XML

次の例は、Java で next() メソッドを使用する方法を示しています。

Java 構成
@Bean
public Job job(JobRepository jobRepository, Step stepA, Step stepB, Step stepC) {
	return new JobBuilder("job", jobRepository)
				.start(stepA)
				.next(stepB)
				.next(stepC)
				.build();
}

次の例は、XML で next 属性を使用する方法を示しています。

XML 構成
<job id="job">
    <step id="stepA" parent="s1" next="stepB" />
    <step id="stepB" parent="s2" next="stepC"/>
    <step id="stepC" parent="s3" />
</job>

上記のシナリオでは、最初にリストされた Step であるため、stepA が最初に実行されます。stepA が正常に完了すると、stepB が実行されます。ただし、step A が失敗すると、Job 全体が失敗し、stepB は実行されません。

Spring Batch XML 名前空間では、構成にリストされている最初のステップは、常に Job によって実行される最初のステップです。他のステップ要素の順序は重要ではありませんが、最初のステップは常に XML で最初に表示される必要があります。

条件付きフロー

前の例では、次の 2 つの可能性しかありません。

  1. step が成功し、次の step を実行する必要があります。

  2. step が失敗したため、job も失敗するはずです。

多くの場合、これで十分です。しかし、step の障害が障害を引き起こすのではなく、異なる step をトリガーするシナリオについてはどうでしょうか? 次の図は、このようなフローを示しています。

Conditional Flow
図 2: 条件付きフロー
  • Java

  • XML

Java API は、フローと、ステップが失敗した場合の処理を指定できる流れるような一連のメソッドを提供します。次の例は、1 つのステップ (stepA) を指定してから、stepA が成功したかどうかに応じて、2 つの異なるステップ (stepB または stepC) のいずれかに進む方法を示しています。

Java 構成
@Bean
public Job job(JobRepository jobRepository, Step stepA, Step stepB, Step stepC) {
	return new JobBuilder("job", jobRepository)
				.start(stepA)
				.on("*").to(stepB)
				.from(stepA).on("FAILED").to(stepC)
				.end()
				.build();
}

より複雑なシナリオを処理するために、Spring Batch XML 名前空間を使用すると、step 要素内に transitions 要素を定義できます。そのような遷移の 1 つが next 要素です。next 属性と同様に、next 要素は、次に実行する Step を Job に通知します。ただし、属性とは異なり、特定の Step で任意の数の next 要素を使用でき、失敗した場合のデフォルトの動作はありません。これは、遷移要素が使用されている場合、Step 遷移のすべての動作を明示的に定義する必要があることを意味します。また、1 つのステップに next 属性と transition 要素の両方を含めることはできないことに注意してください。

次の例に示すように、next 要素は、照合するパターンと次に実行するステップを指定します。

XML 構成
<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>
  • Java

  • XML

java 構成を使用する場合、on() メソッドは、単純なパターンマッチングスキームを使用して、Step の実行結果である ExitStatus と一致します。

XML 構成を使用する場合、遷移要素の on 属性は、単純なパターンマッチングスキームを使用して、Step の実行から生じる ExitStatus を照合します。

パターンでは 2 つの特殊文字のみが許可されます。

  • * はゼロ個以上の文字と一致します

  • ? は正確に 1 文字に一致します

例: c*t は cat および count と一致し、c?t は cat と一致しますが、count とは一致しません。

Step の遷移要素の数に制限はありませんが、要素でカバーされていない ExitStatus が Step 実行の結果である場合、フレームワークは例外をスローし、Job は失敗します。フレームワークは、最も具体的なものから最も具体的でないものへと自動的に遷移を並べ替えます。これは、前の例で順序が stepA に置き換えられたとしても、FAILED の ExitStatus は依然として stepC に移動することを意味します。

バッチステータスと終了ステータス

条件付きフロー用に Job を構成する場合、BatchStatus と ExitStatus の違いを理解することが重要です。BatchStatus は、JobExecution と StepExecution の両方のプロパティである列挙であり、Job または Step のステータスを記録するためにフレームワークによって使用されます。次の値のいずれかです: COMPLETEDSTARTINGSTARTEDSTOPPINGSTOPPEDFAILEDABANDONED または UNKNOWN。それらのほとんどは自明です。COMPLETED は、ステップまたはジョブが正常に完了したときに設定されるステータスであり、FAILED は失敗したときに設定される、などです。

  • Java

  • XML

次の例には、Java 構成を使用する場合の on 要素が含まれています。

...
.from(stepA).on("FAILED").to(stepB)
...

次の例には、XML 構成を使用する場合の next 要素が含まれています。

<next on="FAILED" to="stepB" />

一見すると、on は、それが属する Step の BatchStatus を参照しているように見えます。ただし、実際には Step の ExitStatus を参照しています。名前が示すように、ExitStatus は実行終了後の Step のステータスを表します。

  • Java

  • XML

Java 構成を使用する場合、前述の Java 構成の例に示されている on() メソッドは ExitStatus の終了コードを参照します。

より具体的には、XML 構成を使用する場合、前述の XML 構成の例に示されている next 要素は、ExitStatus の終了コードを参照します。

英語で言うと: 「終了コードが FAILED の場合は stepB に進む」。デフォルトでは、終了コードは常に Step の BatchStatus と同じであるため、前のエントリが機能します。ただし、終了コードを変更する必要がある場合はどうすればよいでしょうか ? サンプルプロジェクト内のスキップサンプルジョブが良い例です。

  • Java

  • XML

次の例は、Java で別の終了コードを操作する方法を示しています。

Java 構成
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2, Step errorPrint1) {
	return new JobBuilder("job", jobRepository)
			.start(step1).on("FAILED").end()
			.from(step1).on("COMPLETED WITH SKIPS").to(errorPrint1)
			.from(step1).on("*").to(step2)
			.end()
			.build();
}

次の例は、XML で別の終了コードを操作する方法を示しています。

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

step1 には 3 つの可能性があります。

  • Step が失敗しました。その場合、ジョブは失敗するはずです。

  • Step は正常に完了しました。

  • Step は正常に完了しましたが、終了コードは COMPLETED WITH SKIPS です。この場合、別のステップを実行してエラーを処理する必要があります。

上記の構成は機能します。ただし、次の例に示すように、レコードをスキップした実行の条件に基づいて終了コードを変更する必要があります。

public class SkipCheckingListener implements StepExecutionListener {
    @Override
    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;
        }
    }
}

上記のコードは StepExecutionListener であり、最初に Step が成功したことを確認し、次に StepExecution のスキップカウントが 0 より大きいかどうかを確認します。両方の条件が満たされた場合、終了コード COMPLETED WITH SKIPS を持つ新しい ExitStatus が返されます。

停止の構成

BatchStatus および ExitStatus の説明の後、Job に対して BatchStatus と ExitStatus がどのように決定されるのか疑問に思うかもしれません。これらのステータスは Step では実行されるコードによって決定されますが、Job のステータスは構成に基づいて決定されます。

これまでのところ、説明したすべてのジョブ構成には、遷移のない最終的な Step が少なくとも 1 つあります。

  • Java

  • XML

次の Java の例では、step が実行された後、Job が終了します。

@Bean
public Job job(JobRepository jobRepository, Step step1) {
	return new JobBuilder("job", jobRepository)
				.start(step1)
				.build();
}

次の XML の例では、step が実行された後、Job が終了します。

<step id="step1" parent="s3"/>

Step に遷移が定義されていない場合、Job のステータスは次のように定義されます。

  • Step が FAILED の ExitStatus で終わる場合、Job の BatchStatus と ExitStatus は両方とも FAILED です。

  • それ以外の場合、Job の BatchStatus と ExitStatus は両方とも COMPLETED です。

バッチジョブを終了するこの方法は、単純な順次ステップジョブなどの一部のバッチジョブには十分ですが、カスタム定義のジョブ停止シナリオが必要になる場合があります。この目的のために、Spring Batch は、(前に説明した next 要素に加えて) Job を停止するための 3 つの遷移要素を提供します。これらの停止要素はそれぞれ、特定の BatchStatus で Job を停止します。停止遷移要素は、Job の Steps の BatchStatus または ExitStatus のいずれにも影響しないことに注意することが重要です。これらの要素は、Job の最終ステータスのみに影響します。例: ジョブ内のすべてのステップのステータスが FAILED であるが、ジョブのステータスが COMPLETED である可能性があります。

ステップで終了

ステップ終了を構成すると、Job が COMPLETED の BatchStatus で停止するように指示されます。COMPLETED のステータスで終了した Job は再開できません (フレームワークは JobInstanceAlreadyCompleteException をスローします)。

  • Java

  • XML

Java 構成を使用する場合、このタスクには end メソッドが使用されます。end メソッドでは、Job の ExitStatus をカスタマイズするために使用できるオプションの exitStatus パラメーターも使用できます。exitStatus 値が指定されていない場合、ExitStatus はデフォルトで COMPLETED になり、BatchStatus と一致します。

XML 構成を使用する場合、このタスクに end エレメントを使用できます。end 要素では、Job の ExitStatus をカスタマイズするために使用できるオプションの exit-code 属性も使用できます。exit-code 属性が指定されていない場合、ExitStatus はデフォルトで COMPLETED になり、BatchStatus と一致します。

次のシナリオを検討してください: step2 が失敗すると、Job は COMPLETED の BatchStatus と COMPLETED の ExitStatus で停止し、step3 は実行されません。それ以外の場合、実行は step3 に移動します。step2 に障害が発生した場合、Job は再起動できないことに注意してください (ステータスが COMPLETED であるため)。

  • Java

  • XML

次の例は、Java でのシナリオを示しています。

@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2, Step step3) {
	return new JobBuilder("job", jobRepository)
				.start(step1)
				.next(step2)
				.on("FAILED").end()
				.from(step2).on("*").to(step3)
				.end()
				.build();
}

次の例は、XML のシナリオを示しています。

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

ステップの失敗

特定のポイントで失敗するようにステップを構成すると、Job が FAILED の BatchStatus で停止するように指示されます。終了とは異なり、Job の障害は Job の再起動を妨げません。

XML 構成を使用する場合、fail 要素は、Job の ExitStatus をカスタマイズするために使用できるオプションの exit-code 属性も許可します。exit-code 属性が指定されていない場合、BatchStatus と一致するように、ExitStatus はデフォルトで FAILED になります。

次のシナリオを検討してください: step2 が失敗すると、Job は FAILED の BatchStatus と EARLY TERMINATION の ExitStatus で停止し、step3 は実行されません。それ以外の場合、実行は step3 に移動します。さらに、step2 が失敗し、Job が再起動された場合、実行は step2 で再び開始されます。

  • Java

  • XML

次の例は、Java でのシナリオを示しています。

Java 構成
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2, Step step3) {
	return new JobBuilder("job", jobRepository)
			.start(step1)
			.next(step2).on("FAILED").fail()
			.from(step2).on("*").to(step3)
			.end()
			.build();
}

次の例は、XML のシナリオを示しています。

XML 構成
<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">

特定のステップでジョブを停止する

特定のステップで停止するようにジョブを構成すると、Job は STOPPED の BatchStatus で停止するように指示されます。Job を停止すると、処理が一時的に中断する可能性があるため、オペレーターは Job を再始動する前に何らかのアクションを取ることができます。

  • Java

  • XML

Java 構成を使用する場合、stopAndRestart メソッドには restart 属性が必要です。この属性は、ジョブの再開時に実行を開始するステップを指定します。

XML 構成を使用する場合、stop 要素には、Job が再開されたときに実行を開始するステップを指定する restart 属性が必要です。

次のシナリオを検討してください: step1 が COMPLETE で終了すると、ジョブは停止します。再起動すると、step2 で実行が開始されます。

  • Java

  • XML

次の例は、Java でのシナリオを示しています。

@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2) {
	return new JobBuilder("job", jobRepository)
			.start(step1).on("COMPLETED").stopAndRestart(step2)
			.end()
			.build();
}

次のリストは、XML でのシナリオを示しています。

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

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

プログラムによるフローの決定

場合によっては、次に実行するステップを決定するために、ExitStatus よりも多くの情報が必要になることがあります。この場合、次の例に示すように、JobExecutionDecider を使用して決定を支援できます。

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

  • XML

次の例では、Java 構成を使用するときに、JobExecutionDecider を実装する Bean が next 呼び出しに直接渡されます。

Java 構成
@Bean
public Job job(JobRepository jobRepository, MyDecider decider, Step step1, Step step2, Step step3) {
	return new JobBuilder("job", jobRepository)
			.start(step1)
			.next(decider).on("FAILED").to(step2)
			.from(decider).on("COMPLETED").to(step3)
			.end()
			.build();
}

次のサンプルジョブ構成では、decision は、使用する決定者とすべての遷移を指定します。

XML 構成
<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"/>

フローの分割

これまでに説明したすべてのシナリオには、Job が関与しており、その手順は一度に 1 つずつ線形に実行されます。この典型的なスタイルに加えて、Spring Batch では、並列フローでジョブを構成することもできます。

  • Java

  • XML

Java ベースの構成により、提供されたビルダーを介して分割を構成できます。次の例に示すように、split 要素には 1 つ以上の flow 要素が含まれており、個別のフロー全体を定義できます。split 要素には、next 属性や nextend、または fail 要素など、前述の移行要素を含めることもできます。

@Bean
public Flow flow1(Step step1, Step step2) {
	return new FlowBuilder<SimpleFlow>("flow1")
			.start(step1)
			.next(step2)
			.build();
}

@Bean
public Flow flow2(Step step3) {
	return new FlowBuilder<SimpleFlow>("flow2")
			.start(step3)
			.build();
}

@Bean
public Job job(JobRepository jobRepository, Flow flow1, Flow flow2, Step step4) {
	return new JobBuilder("job", jobRepository)
				.start(flow1)
				.split(new SimpleAsyncTaskExecutor())
				.add(flow2)
				.next(step4)
				.end()
				.build();
}

XML 名前空間では、split 要素を使用できます。次の例に示すように、split 要素には 1 つ以上の flow 要素が含まれており、個別のフロー全体を定義できます。split 要素には、next 属性や nextend、または fail 要素など、前述の移行要素を含めることもできます。

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

フロー定義とジョブ間の依存関係の外部化

ジョブ内のフローの一部は、個別の Bean 定義として外部化してから再利用できます。これには 2 つの方法があります。1 つ目は、他の場所で定義されたフローへの参照としてフローを宣言することです。

  • Java

  • XML

次の Java の例は、別の場所で定義されたフローへの参照としてフローを宣言する方法を示しています。

Java 構成
@Bean
public Job job(JobRepository jobRepository, Flow flow1, Step step3) {
	return new JobBuilder("job", jobRepository)
				.start(flow1)
				.next(step3)
				.end()
				.build();
}

@Bean
public Flow flow1(Step step1, Step step2) {
	return new FlowBuilder<SimpleFlow>("flow1")
			.start(step1)
			.next(step2)
			.build();
}

次の XML の例は、他の場所で定義されたフローへの参照としてフローを宣言する方法を示しています。

XML 構成
<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>

前の例に示すように、外部フローを定義すると、インラインで宣言されているかのように、外部フローからのステップがジョブに挿入されます。このようにして、多くのジョブが同じテンプレートフローを参照し、そのようなテンプレートを異なる論理フローに構成できます。これは、個々のフローの統合テストを分離する良い方法でもあります。

外部化されたフローの他の形式は、JobStep を使用することです。JobStep は FlowStep に似ていますが、実際には、指定されたフロー内のステップに対して個別のジョブ実行を作成して起動します。

  • Java

  • XML

次の例は、Java での JobStep の例を示しています。

Java 構成
@Bean
public Job jobStepJob(JobRepository jobRepository, Step jobStepJobStep1) {
	return new JobBuilder("jobStepJob", jobRepository)
				.start(jobStepJobStep1)
				.build();
}

@Bean
public Step jobStepJobStep1(JobRepository jobRepository, JobLauncher jobLauncher, Job job, JobParametersExtractor jobParametersExtractor) {
	return new StepBuilder("jobStepJobStep1", jobRepository)
				.job(job)
				.launcher(jobLauncher)
				.parametersExtractor(jobParametersExtractor)
				.build();
}

@Bean
public Job job(JobRepository jobRepository) {
	return new JobBuilder("job", jobRepository)
				// ...
				.build();
}

@Bean
public DefaultJobParametersExtractor jobParametersExtractor() {
	DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor();

	extractor.setKeys(new String[]{"input.file"});

	return extractor;
}

次の例では、XML での JobStep の例を示しています。

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

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

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

ジョブパラメーターエクストラクターは、実行される Job の JobParameters に Step の ExecutionContext を変換する方法を決定する戦略です。JobStep は、ジョブとステップを監視およびレポートするためのより詳細なオプションが必要な場合に役立ちます。JobStep を使用すると、多くの場合、「ジョブ間の依存関係を作成するにはどうすればよいですか ? 」という質問に対する良い答えになります。これは、大規模なシステムを小さなモジュールに分割し、ジョブの流れを制御するための優れた方法です。