ジョブの構成と実行

ドメインセクションでは、次の図をガイドとして使用して、全体的なアーキテクチャ設計について説明しました。

Figure 2.1: Batch Stereotypes
図 1: バッチステレオタイプ

Job オブジェクトはステップの単純なコンテナーのように見えるかもしれませんが、多くの構成オプションに注意する必要があります。さらに、Job を実行する方法と、その実行中にそのメタデータを保存する方法について、多くのオプションを考慮する必要があります。この章では、Job のさまざまな構成オプションと実行時の問題について説明します。

ジョブの構成

Job インターフェースには複数の実装があります。ただし、ビルダーは構成の違いを抽象化します。次の例では、footballJob を作成します。

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}

Job (および通常はその中の Step)には JobRepository が必要です。JobRepository の構成は、Java Configuration を介して処理されます。

前の例は、3 つの Step インスタンスで構成される Job を示しています。ジョブ関連ビルダーには、並列化 (Split)、宣言型フロー制御 (Decision)、およびフロー定義の外部化 (Flow) に役立つ他の要素を含めることもできます。

Job インターフェースには複数の実装があります。ただし、名前空間は構成の違いを抽象化します。必要な依存関係は、名前、JobRepositoryStep インスタンスのリストの 3 つだけです。次の例では、footballJob を作成します。

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

ここの例では、親 Bean 定義を使用してステップを作成します。特定のステップの詳細をインラインで宣言する場合のその他のオプションについては、ステップ構成に関するセクションを参照してください。XML 名前空間は、デフォルトで jobRepository という ID を持つリポジトリを参照します。これは実用的なデフォルトです。ただし、明示的にオーバーライドできます。

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

手順に加えて、ジョブ構成には、並列化(<split>)、宣言型フロー制御(<decision>)、フロー定義の外部化(<flow/>)に役立つ他の要素を含めることができます。

再起動性

バッチジョブを実行する際の重要な課題の 1 つは、再起動時の Job の動作に関するものです。特定の JobInstance に対して JobExecution がすでに存在する場合、Job の起動は「再起動」と見なされます。理想的には、すべてのジョブが中断したところから開始できる必要がありますが、これが不可能なシナリオもあります。このシナリオでは、新しい JobInstance が作成されることを確認するのは完全に開発者の責任です。ただし、Spring Batch はいくつかの助けを提供します。Job を再起動してはならず、常に新しい JobInstance の一部として実行する必要がある場合は、再起動可能プロパティを false に設定できます。

次の例は、XML で restartable フィールドを false に設定する方法を示しています。

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

次の例は、Java で restartable フィールドを false に設定する方法を示しています。

Java Configuration
@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
                     .preventRestart()
                     ...
                     .build();
}

別の言い方をすれば、restartable を false に設定すると、「この Job は再起動をサポートしていません」という意味になります。再起動できない Job を再起動すると、JobRestartException がスローされます。次の JUnit コードにより、例外がスローされます。

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) {
    // expected
}

再起動不可能なジョブの JobExecution を最初に作成しようとしても、課題は発生しません。ただし、2 回目の試行では JobRestartException がスローされます。

ジョブ実行のインターセプト

Job の実行中に、カスタムコードを実行できるように、ライフサイクル内のさまざまなイベントの通知を受け取ると便利な場合があります。SimpleJob は、適切なタイミングで JobListener を呼び出すことにより、これを可能にします。

public interface JobExecutionListener {

    void beforeJob(JobExecution jobExecution);

    void afterJob(JobExecution jobExecution);
}

ジョブにリスナーを設定することにより、JobListeners を SimpleJob に追加できます。

次の例は、リスナー要素を XML ジョブ定義に追加する方法を示しています。

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

次の例は、Java ジョブ定義にリスナーメソッドを追加する方法を示しています。

Java Configuration
@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
                     .listener(sampleListener())
                     ...
                     .build();
}

Job の成功または失敗に関係なく、afterJob メソッドが呼び出されることに注意してください。成功または失敗を判断する必要がある場合は、JobExecution からその情報を取得できます。

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

このインターフェースに対応するアノテーションは次のとおりです。

  • @BeforeJob

  • @AfterJob

親ジョブからの継承

ジョブのグループが類似しているが同一ではない構成を共有している場合、具体的な Job インスタンスがプロパティを継承できる「親」 Job を定義すると役立つ場合があります。Java のクラス継承と同様に、「子」 Job はその要素と属性を親のものと結合します。

次の例では、baseJob はリスナーのリストのみを定義する抽象 Job 定義です。Job (job1) は、baseJob からリスナーのリストを継承し、独自のリスナーリストとマージして、2 つのリスナーと 1 つの Step (step1) を持つ Job を生成する具体的な定義です。

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

詳細については、親ステップからの継承のセクションを参照してください。

JobParametersValidator

XML 名前空間で宣言されたジョブ、または AbstractJob のサブクラスを使用して宣言されたジョブは、オプションで、実行時にジョブパラメーターのバリデーターを宣言できます。これは、たとえば、ジョブがすべての必須パラメーターで開始されていることを表明する必要がある場合に役立ちます。単純な必須パラメーターとオプションパラメーターの組み合わせを制約するために使用できる DefaultJobParametersValidator があります。より複雑な制約については、インターフェースを自分で実装できます。

次の例に示すように、バリデーターの構成は、ジョブの子要素を介して XML 名前空間を介してサポートされます。

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

バリデーターは、参照として(前に示したように)、または beans 名前空間でネストされた Bean 定義として指定できます。

バリデーターの構成は、Java ビルダーを介してサポートされます。

@Bean
public Job job1(JobRepository jobRepository) {
    return new JobBuilder("job1", jobRepository)
                     .validator(parametersValidator())
                     ...
                     .build();
}

Java 構成

Spring 3 は、XML の代わりに Java を使用してアプリケーションを構成する機能をもたらしました。Spring Batch 2.2.0 の時点で、同じ Java 構成を使用してバッチジョブを構成できます。Java ベースの構成には、@EnableBatchProcessing アノテーションと 2 つのビルダーの 3 つのコンポーネントがあります。

@EnableBatchProcessing アノテーションは、Spring ファミリーの他の @Enable* アノテーションと同様に機能します。この場合、@EnableBatchProcessing は、バッチジョブを構築するための基本構成を提供します。この基本構成内で、StepScope および JobScope のインスタンスが作成され、多数の Bean がオートワイヤーできるようになります。

  • JobRepositoryjobRepository という名前の Bean

  • JobLauncherjobLauncher という名前の Bean

  • JobRegistryjobRegistry という名前の Bean

  • JobExplorerjobExplorer という名前の Bean

  • JobOperatorjobOperator という名前の Bean

デフォルトの実装は、前のリストに記載されている Bean を提供し、DataSource および PlatformTransactionManager をコンテキスト内の Bean として提供する必要があります。データソースとトランザクションマネージャーは、JobRepository および JobExplorer インスタンスによって使用されます。デフォルトでは、dataSource という名前のデータソースと transactionManager という名前のトランザクションマネージャーが使用されます。@EnableBatchProcessing アノテーションの属性を使用して、これらの Bean をカスタマイズできます。次の例は、カスタムデータソースとトランザクションマネージャーを提供する方法を示しています。

@Configuration
@EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager")
public class MyJobConfiguration {

	@Bean
	public DataSource batchDataSource() {
		return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
				.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
				.generateUniqueName(true).build();
	}

	@Bean
	public JdbcTransactionManager batchTransactionManager(DataSource dataSource) {
		return new JdbcTransactionManager(dataSource);
	}

	public Job job(JobRepository jobRepository) {
		return new JobBuilder("myJob", jobRepository)
				//define job flow as needed
				.build();
	}

}
@EnableBatchProcessing アノテーションが必要な構成クラスは 1 つだけです。クラスにアノテーションを付けると、前述のすべての構成が完了します。

v5.0 から始めて、ベースインフラストラクチャ Bean を構成する代替のプログラムによる方法が DefaultBatchConfiguration クラスを通じて提供されます。このクラスは、@EnableBatchProcessing によって提供されるものと同じ Bean を提供し、バッチジョブを構成するための基本クラスとして使用できます。次のスニペットは、その使用方法の典型的な例です。

@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {

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

}

データソースとトランザクションマネージャーは、アプリケーションコンテキストから解決され、ジョブリポジトリとジョブエクスプローラーに設定されます。必要な setter をオーバーライドすることにより、任意のインフラストラクチャ Bean の構成をカスタマイズできます。次の例は、たとえば文字エンコーディングをカスタマイズする方法を示しています。

@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {

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

	@Override
	protected Charset getCharset() {
		return StandardCharsets.ISO_8859_1;
	}
}
@EnableBatchProcessing は DefaultBatchConfiguration と一緒に使用しないでください。@EnableBatchProcessing を介して Spring Batch を構成する宣言的な方法を使用するか、DefaultBatchConfiguration を継承するプログラムによる方法を使用する必要がありますが、両方の方法を同時に使用することはできません。

JobRepository の構成

@EnableBatchProcessing を使用すると、JobRepository が提供されます。このセクションでは、独自の構成方法について説明します。

前述のように、JobRepository は、JobExecution や StepExecution など、Spring Batch 内のさまざまな永続化ドメインオブジェクトの基本的な CRUD 操作に使用されます。JobLauncherJobStep などの主要なフレームワーク機能の多くで必要とされます。

バッチ名前空間は、JobRepository 実装とその協力者の実装の詳細の多くを抽象化します。ただし、次の例に示すように、使用可能な構成オプションがまだいくつかあります。

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

id 以外には、前述の構成オプションは必要ありません。設定されていない場合は、前に示したデフォルトが使用されます。max-varchar-length のデフォルトは 2500 です。これは、サンプルスキーマスクリプトの長い VARCHAR 列の長さです。

dataSource と transactionManager 以外には、前述の構成オプションは必要ありません。設定されていない場合は、前に示したデフォルトが使用されます。varchar の最大長のデフォルトは 2500 です。これは、サンプルスキーマスクリプトの長い VARCHAR 列の長さです。

JobRepository のトランザクション構成

名前空間または提供された FactoryBean が使用される場合、トランザクションに関するアドバイスがリポジトリの周囲に自動的に作成されます。これは、障害後の再起動に必要な状態を含むバッチメタデータが正しく保持されるようにするためです。リポジトリメソッドがトランザクション対応でない場合、フレームワークの動作は明確に定義されていません。create* メソッド属性の分離レベルは、ジョブの起動時に 2 つのプロセスが同時に同じジョブを起動しようとしても、1 つのみが成功するように個別に指定されます。そのメソッドのデフォルトの分離レベルは SERIALIZABLE で、これは非常に積極的です。通常、READ_COMMITTED も同様に機能します。READ_UNCOMMITTED は、2 つのプロセスがこのように衝突する可能性が低い場合には問題ありません。ただし、create* メソッドの呼び出しは非常に短いため、データベースプラットフォームがサポートしている限り、SERIALIZED が問題を引き起こす可能性はほとんどありません。ただし、この設定をオーバーライドできます。

次の例は、XML の分離レベルをオーバーライドする方法を示しています。

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

次の例は、Java で分離レベルをオーバーライドする方法を示しています。

Java Configuration
@Configuration
@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_REPEATABLE_READ")
public class MyJobConfiguration {

   // job definition

}

名前空間を使用しない場合は、AOP を使用してリポジトリのトランザクション動作も構成する必要があります。

次の例は、XML でリポジトリのトランザクション動作を構成する方法を示しています。

XML Configuration
<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-tx と spring-aop (または Spring 全体) がクラスパスにあることを確認してください。

次の例は、Java でリポジトリのトランザクション動作を構成する方法を示しています。

Java Configuration
@Bean
public TransactionProxyFactoryBean baseProxy() {
	TransactionProxyFactoryBean transactionProxyFactoryBean = new TransactionProxyFactoryBean();
	Properties transactionAttributes = new Properties();
	transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");
	transactionProxyFactoryBean.setTransactionAttributes(transactionAttributes);
	transactionProxyFactoryBean.setTarget(jobRepository());
	transactionProxyFactoryBean.setTransactionManager(transactionManager());
	return transactionProxyFactoryBean;
}

テーブルプレフィックスの変更

JobRepository のもう 1 つの変更可能なプロパティは、メタデータテーブルのテーブルプレフィックスです。デフォルトでは、それらはすべて BATCH_ で始まります。BATCH_JOB_EXECUTION と BATCH_STEP_EXECUTION は 2 つの例です。ただし、このプレフィックスを変更する理由が考えられます。スキーマ名をテーブル名の前に追加する必要がある場合、または同じスキーマ内に複数のメタデータテーブルのセットが必要な場合は、テーブルのプレフィックスを変更する必要があります。

次の例は、XML でテーブルプレフィックスを変更する方法を示しています。

XML Configuration
<job-repository id="jobRepository"
                table-prefix="SYSTEM.TEST_" />

次の例は、Java でテーブルプレフィックスを変更する方法を示しています。

Java Configuration
@Configuration
@EnableBatchProcessing(tablePrefix = "SYSTEM.TEST_")
public class MyJobConfiguration {

   // job definition

}

上記の変更により、メタデータテーブルへのすべてのクエリには SYSTEM.TEST_ というプレフィックスが付きます。BATCH_JOB_EXECUTION は SYSTEM.TEST_JOB_EXECUTION と呼ばれます。

テーブルプレフィックスのみが設定可能です。テーブル名と列名は違います。

リポジトリ内の非標準データベース型

サポートされているプラットフォームのリストにないデータベースプラットフォームを使用する場合、SQL バリアントが十分に近い場合は、サポートされている型のいずれかを使用できる場合があります。これを行うには、名前空間のショートカットの代わりに生の JobRepositoryFactoryBean を使用し、それを使用してデータベースの種類を最も近いものに設定します。

次の例は、JobRepositoryFactoryBean を使用してデータベース型を XML で最も一致するものに設定する方法を示しています。

XML Configuration
<bean id="jobRepository" class="org...JobRepositoryFactoryBean">
    <property name="databaseType" value="db2"/>
    <property name="dataSource" ref="dataSource"/>
</bean>

次の例は、JobRepositoryFactoryBean を使用してデータベース型を Java で最も近いものに設定する方法を示しています。

Java Configuration
@Bean
public JobRepository jobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setDatabaseType("db2");
    factory.setTransactionManager(transactionManager);
    return factory.getObject();
}

データベース型が指定されていない場合、JobRepositoryFactoryBean は DataSource からデータベース型を自動検出しようとします。プラットフォーム間の主な違いは、主に主キーをインクリメントする戦略によって説明されるため、(Spring Framework の標準実装の 1 つを使用して) incrementerFactory もオーバーライドする必要があることがよくあります。

それでもうまくいかない場合、または RDBMS を使用していない場合、唯一のオプションは、SimpleJobRepository が依存するさまざまな Dao インターフェースを実装し、通常の Spring 方法で手動で接続することです。

JobLauncher の構成

@EnableBatchProcessing を使用すると、JobRegistry が提供されます。このセクションでは、独自の構成方法について説明します。

JobLauncher インターフェースの最も基本的な実装は TaskExecutorJobLauncher です。その唯一の必要な依存関係は JobRepository (実行を取得するために必要) です。

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

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

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

Java Configuration
...
@Bean
public JobLauncher jobLauncher() throws Exception {
	TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
	jobLauncher.setJobRepository(jobRepository);
	jobLauncher.afterPropertiesSet();
	return jobLauncher;
}
...

取得された JobExecution は Job の execute メソッドに渡され、最終的に次の図に示すように JobExecution が呼び出し元に返されます。

Job Launcher Sequence
図 2: ジョブランチャーシーケンス

シーケンスは簡単で、スケジューラーから起動するとうまく機能します。ただし、HTTP リクエストから起動しようとすると問題が発生します。このシナリオでは、TaskExecutorJobLauncher がすぐに呼び出し元に戻るように、起動を非同期で行う必要があります。これは、実行時間の長いプロセス (バッチジョブなど) に必要な時間、HTTP リクエストを開いたままにしておくのは適切ではないためです。次の図は、シーケンスの例を示しています。

Async Job Launcher Sequence
図 3: 非同期ジョブランチャーシーケンス

TaskExecutor を構成することにより、このシナリオを可能にするように TaskExecutorJobLauncher を構成できます。

次の XML の例では、すぐに戻るように TaskExecutorJobLauncher を構成しています。

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

次の Java の例では、すぐに戻るように TaskExecutorJobLauncher を構成します。

Java Configuration
@Bean
public JobLauncher jobLauncher() {
	TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
	jobLauncher.setJobRepository(jobRepository());
	jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
	jobLauncher.afterPropertiesSet();
	return jobLauncher;
}

Spring TaskExecutor インターフェースの任意の実装を使用して、ジョブの非同期実行方法を制御できます。

ジョブを実行する

少なくとも、バッチジョブを起動するには、起動する Job と JobLauncher の 2 つが必要です。どちらも同じコンテキストまたは異なるコンテキストに含めることができます。例: コマンドラインからジョブを起動すると、新しい JVM が各 Job に対してインスタンス化されます。すべてのジョブには独自の JobLauncher があります。ただし、HttpRequest のスコープ内にある Web コンテナー内から実行する場合、通常、複数のリクエストがジョブを起動するために呼び出す 1 つの JobLauncher (非同期ジョブ起動用に構成) があります。

コマンドラインからジョブを実行する

エンタープライズスケジューラからジョブを実行する場合は、コマンドラインが主要なインターフェースになります。これは、ほとんどのスケジューラ ( Quartz を除き、NativeJob を使用しない場合) が、主にシェルスクリプトで開始されるオペレーティングシステムプロセスと直接連携するためです。Perl や Ruby などのシェルスクリプトや、Ant や Maven などのビルドツール以外にも、Java プロセスを起動する方法は多数あります。ただし、ほとんどの人はシェルスクリプトに精通しているため、この例ではシェルスクリプトに焦点を当てています。

CommandLineJobRunner

ジョブを起動するスクリプトは Java 仮想マシンを起動する必要があるため、プライマリエントリポイントとして機能する main メソッドを持つクラスが必要です。Spring Batch は、この目的に役立つ実装を提供します: CommandLineJobRunner。これは、アプリケーションをブートストラップする 1 つの方法にすぎないことに注意してください。Java プロセスを起動する方法は多数ありますが、このクラスを決定的なものと見なすべきではありません。CommandLineJobRunner は次の 4 つのタスクを実行します。

  • 適切な ApplicationContext をロードします。

  • コマンドライン引数を JobParameters に解析します。

  • 引数に基づいて適切なジョブを見つけます。

  • アプリケーションコンテキストで提供される JobLauncher を使用して、ジョブを起動します。

これらのタスクはすべて、渡された引数のみで実行されます。次の表に、必要な引数を示します。

表 1: CommandLineJobRunner 引数

jobPath

ApplicationContext の作成に使用される XML ファイルの場所。このファイルには、完全な Job を実行するために必要なすべてが含まれている必要があります。

jobName

実行するジョブの名前。

これらの引数は、最初にパス、2 番目に名前を付けて渡す必要があります。これらの後のすべての引数は、ジョブパラメーターと見なされ、JobParameters オブジェクトに変換され、name=value の形式である必要があります。

次の例は、ジョブパラメーターとして XML で定義されたジョブに渡される日付を示しています。

<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date=2007-05-05,java.time.LocalDate

次の例は、Java で定義されたジョブにジョブパラメーターとして渡された日付を示しています。

<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay schedule.date=2007-05-05,java.time.LocalDate

デフォルトでは、CommandLineJobRunner は DefaultJobParametersConverter を使用します。この DefaultJobParametersConverter は、キーと値のペアを識別ジョブパラメーターに暗黙的に変換します。ただし、それぞれ true または false の接尾辞を付けることで、識別するジョブパラメーターと識別しないジョブパラメーターを明示的に指定できます。

次の例では、schedule.date は識別ジョブパラメーターですが、vendor.id はそうではありません。

<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay \
                                 schedule.date=2007-05-05,java.time.LocalDate,true \
                                 vendor.id=123,java.lang.Long,false
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay \
                                 schedule.date=2007-05-05,java.time.LocalDate,true \
                                 vendor.id=123,java.lang.Long,false

カスタム JobParametersConverter を使用して、この動作をオーバーライドできます。

ほとんどの場合、マニフェストを使用して jar で main クラスを宣言する必要があります。ただし、簡単にするために、クラスを直接使用しました。この例では、バッチのドメイン言語の EndOfDay 例を使用します。最初の引数は endOfDayJob.xml で、これは Job を含む Spring ApplicationContext です。2 番目の引数 endOfDay, はジョブ名を表します。最後の引数 schedule.date=2007-05-05,java.time.LocalDate は、java.time.LocalDate 型の JobParameter オブジェクトに変換されます。

次の例は、XML での endOfDay の構成例を示しています。

<job id="endOfDay">
    <step id="step1" parent="simpleStep" />
</job>

<!-- Launcher details removed for clarity -->
<beans:bean id="jobLauncher"
         class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher" />

ほとんどの場合、マニフェストを使用して jar で main クラスを宣言することをお勧めします。ただし、簡単にするために、クラスを直接使用しました。この例では、バッチのドメイン言語の EndOfDay 例を使用します。最初の引数は io.spring.EndOfDayJobConfiguration で、ジョブを含む構成クラスの完全修飾クラス名です。2 番目の引数 endOfDay はジョブ名を表します。最後の引数 schedule.date=2007-05-05,java.time.LocalDate は、java.time.LocalDate 型の JobParameter オブジェクトに変換されます。

次の例は、Java での endOfDay の構成例を示しています。

@Configuration
@EnableBatchProcessing
public class EndOfDayJobConfiguration {

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

    @Bean
    public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilder("step1", jobRepository)
    				.tasklet((contribution, chunkContext) -> null, transactionManager)
    				.build();
    }
}

上記の例は、一般に Spring Batch でバッチジョブを実行するための要件が他にもたくさんあるため、非常に単純化されていますが、CommandLineJobRunner の 2 つの主要な要件である Job と JobLauncher を示すのに役立ちます。

終了コード

コマンドラインからバッチジョブを起動する場合、エンタープライズスケジューラがよく使用されます。ほとんどのスケジューラはかなり馬鹿げており、プロセスレベルでしか機能しません。これは、一部のオペレーティングシステムプロセス (呼び出したシェルスクリプトなど) しか認識していないことを意味します。このシナリオでは、ジョブの成功または失敗についてスケジューラに返信する唯一の方法は、リターンコードを使用することです。リターンコードは、実行の結果を示すためにプロセスによってスケジューラに返される番号です。最も単純なケースでは、0 は成功、1 は失敗です。ただし、「ジョブ A が 4 を返す場合はジョブ B を開始し、5 を返す場合はジョブ C を開始する」など、より複雑なシナリオが存在する場合もあります。この型の動作はスケジューラレベルで構成されますが、Spring Batch などの処理フレームワークが特定のバッチジョブの終了コードの数値表現を返す方法を提供することが重要です。Spring Batch では、これは ExitStatus 内にカプセル化されます。これについては、第 5 章で詳しく説明します。終了コードについて説明する目的で知っておくべき唯一の重要なことは、ExitStatus にはフレームワークによって設定される終了コードプロパティがあることです (または開発者) であり、JobLauncher から返される JobExecution の一部として返されます。CommandLineJobRunner は、ExitCodeMapper インターフェースを使用して、この文字列値を数値に変換します。

public interface ExitCodeMapper {

    public int intValue(String exitCode);

}

ExitCodeMapper の本質的な契約は、文字列の終了コードを指定すると、数値表現が返されるということです。ジョブランナーが使用するデフォルトの実装は SimpleJvmExitCodeMapper で、完了の場合は 0、一般的なエラーの場合は 1、指定されたコンテキストで Job が見つからないなどのジョブランナーのエラーの場合は 2 を返します。上記の 3 つの値よりも複雑なものが必要な場合は、ExitCodeMapper インターフェースのカスタム実装を提供する必要があります。CommandLineJobRunner は ApplicationContext を作成するクラスであるため、「一緒に接続」することはできないため、上書きする必要がある値はすべてオートワイヤーする必要があります。これは、ExitCodeMapper の実装が BeanFactory 内で見つかった場合、コンテキストが作成された後にランナーに注入されることを意味します。独自の ExitCodeMapper を提供するために行う必要があるのは、実装をルートレベル Bean として宣言し、それがランナーによってロードされる ApplicationContext の一部であることを確認することだけです。

Web コンテナー内からのジョブの実行

以前は、前述のように、オフライン処理 (バッチジョブなど) はコマンドラインから起動されていました。ただし、HttpRequest からの起動がより適切なオプションである場合が多くあります。このようなユースケースの多くには、レポート、アドホックジョブの実行、および Web アプリケーションのサポートが含まれます。バッチジョブは (定義上) 長時間実行されるため、最も重要な関心事は、ジョブを非同期で起動することです。

Async Job Launcher Sequence from web container
図 4: Web コンテナーからの非同期ジョブランチャーシーケンス

この場合のコントローラーは Spring MVC コントローラーです。Spring MVC の詳細については、Spring Framework リファレンスガイドを参照してください。コントローラーは、非同期で起動するように構成された JobLauncher を使用して Job を起動し、すぐに JobExecution を返します。Job はまだ実行されている可能性があります。ただし、この非ブロック動作により、コントローラーはすぐに戻ることができます。これは、HttpRequest を処理するときに必要です。次のリストは例を示しています。

@Controller
public class JobLauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

高度なメタデータの使用

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

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

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

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

以降のセクションで説明する 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 を使用して簡単に構成できます。

次の例は、XML で JobExplorer を構成する方法を示しています。

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

次の例は、Java で JobExplorer を構成する方法を示しています。

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

この章の前半、異なるバージョンまたはスキーマを許可するために、JobRepository のテーブルプレフィックスを変更できることに注意しました。JobExplorer は同じテーブルで動作するため、プレフィックスを設定する機能も必要です。

次の例は、XML で JobExplorer のテーブルプレフィックスを設定する方法を示しています。

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

次の例は、Java で JobExplorer のテーブルプレフィックスを設定する方法を示しています。

Java Configuration
...
// 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();
}
...

JobRegistry

JobRegistry (およびその親インターフェースである JobLocator) は必須ではありませんが、コンテキストで使用可能なジョブを追跡したい場合に役立ちます。また、ジョブが別の場所 (子コンテキストなど) で作成された場合に、アプリケーションコンテキストで一元的にジョブを収集する場合にも役立ちます。カスタム JobRegistry 実装を使用して、登録されているジョブの名前やその他のプロパティを操作することもできます。フレームワークによって提供される実装は 1 つだけで、これはジョブ名からジョブインスタンスへの単純なマップに基づいています。

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

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

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

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

JobRegistryBeanPostProcessor

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

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

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

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

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

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

AutomaticJobRegistrar

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

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

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

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

Java Configuration
@Bean
public AutomaticJobRegistrar registrar() {

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

}

レジストラには 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) には多くの依存関係があります。

次の例は、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>

次の例は、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;
 }

バージョン 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 つインクリメントされて返されます。

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

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

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

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

ジョブの停止

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 に変更してください。