Anarchy In the 1K

Spring Boot Batchの実行とメタデータテーブル

目次

背景

 必要に迫られSrping Boot Batchの勉強をはじめました。今回は、起動方法と自動で作成されるメタデータテーブルの確認を行います。

ハンズオン

 早速ですが動くものを作ります。

事前準備

プロジェクトの作成

 spring initializrで以下の通りプロジェクトを作成します。 f:id:fujiU:20200731085332p:plain

DBの用意

 こちらの記事を元に、PostgreSQLの用意を行います。

コーディング

 上記で作成したプロジェクトを元に以下の通りコーディングを行います。

Tasklet

 Spring Batchでは、タスクレットとチャンクという2つのモデルが存在します。今回は前者を採用します。また、今回は実行時にコマンドライン引数を渡す為、受け取れる様にしておきます。

@Component
@Scope("step") // (1)
public class HelloTasklet implements Tasklet {
  @Value("#{jobParameters[name] ?: \"Nanashi\"}") // (2)
  private String name;

  @Override
  public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext)
      throws Exception {
    System.out.println("Hello: " + name);
    return RepeatStatus.FINISHED; // (3)
  }
}
  1. JobParametersから値を受け取るに当たり、@Scope("step")を付与する必要があります。詳しくは、こちらを参照ください。
  2. JobParametersから、パラメータ名nameの値を取得します。引数が無い場合、デフォルト値として"Nanashi"が設定されます。
  3. 戻り値に指定可能な値は、RepeatStatus.CONTINUABLE(処理継続)とRepeatStatus.FINISHED(処理終了)の2値になります。今回は、終了するので後者を選択します。

JavaConfig

@Configuration // (1)
@EnableBatchProcessing // (2)
@AllArgsConstructor
public class BatchConfig {
  private final JobBuilderFactory jobBuilderFactory;
  private final StepBuilderFactory stepBuilderFactory;
  private final HelloTasklet helloTasklet;

  @Bean
  public Step helloStep() { // (3)
    return stepBuilderFactory.get("helloStep").tasklet(helloTasklet).build();
  }
}

  @Bean
  public Job helloJob(Step helloStep) { // (4)
    return jobBuilderFactory.get("helloJob").flow(helloStep).end().build();
  }
  1. Java上で設定を行う為に、@Configurationを付与します。
  2. Spring Batchで必要になるBean定義を自動で行う為に、@EnableBatchProcessingを付与します。
  3. ステップの定義を行う。
  4. 上記で定義したステップを保持するジョブを定義する。ジョブとステップの関係はこちらを参照ください。

エントリポイント

@SpringBootApplication
public class SpringBootBatchSample01Application {
  public static void main(String[] args) {
    SpringApplication.run(SpringBootBatchSample01Application.class, args);
  }
}

設定ファイル

 src/main/resourcesapplication.ymlを作成し、以下の通り記載します。元々存在したapplication.propertiesは削除します。

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
    driver-class-name: org.postgresql.Driver
    username: postgres
    password: password
  batch:
    initialize-schema: always # DBを初期化する為の設定

実行

1回目

実行前のDB

 まず、アプリケーションを実行する前にDBの状態を確認しましょう。コンテナ上のDBにログインし*1、以下コマンドを発行します。結果、以下の通りテーブルが1つも存在しないことが確認できます。

postgres=# \dt
Did not find any relations.

アプリケーションの実行

 次に、プロジェクトのルートディレクトリでmvn spring-boot:runを発行します。結果、以下の通りデフォルト値のNanashiが出力されました。

...
2020-08-01 13:56:44.433  INFO 14659 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [helloWorldStep]
Hello: Nanashi
2020-08-01 13:56:44.476  INFO 14659 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [helloWorldStep] executed in 43ms
...

実行後のDB

 アプリケーション実行後のDBの状態を再度確認してみましょう。結果、以下の通り6つのテーブルが作成されています。各テーブルの説明に関して、こちらこちらをご覧ください。

postgres=# \dt 
                    List of relations
 Schema |             Name             | Type  |  Owner   
--------+------------------------------+-------+----------
 public | batch_job_execution          | table | postgres
 public | batch_job_execution_context  | table | postgres
 public | batch_job_execution_params   | table | postgres
 public | batch_job_instance           | table | postgres
 public | batch_step_execution         | table | postgres
 public | batch_step_execution_context | table | postgres
(6 rows)

batch_job_instanceテーブルとbatch_job_executionテーブルの中身を確認してみましょう。以下の通り実行が記録されています。

postgres=# select * from batch_job_instance;
 job_instance_id | version |   job_name    |             job_key
-----------------+---------+---------------+----------------------------------
               1 |       0 | helloWorldJob | d41d8cd98f00b204e9800998ecf8427e
(1 row)

postgres=# select * from batch_job_execution;
 job_execution_id | version | job_instance_id |       create_time       |       start_time        |        end_time         |  status   | exit_code | exit_message |      last_updated       | job_configuration_location
------------------+---------+-----------------+-------------------------+-------------------------+-------------------------+-----------+-----------+--------------+-------------------------+----------------------------
                1 |       2 |               1 | 2020-07-30 08:36:10.414 | 2020-07-30 08:36:10.445 | 2020-07-30 08:36:10.538 | COMPLETED | COMPLETED |              | 2020-07-30 08:36:10.538 |
(1 row)

2回目

アプリケーションの実行

 再度、mvn spring-boot:runを発行し、アプリケーションを実行してみましょう。結果、以下ログが出力されジョブが起動しませんでした。

2020-07-31 08:02:03.801  INFO 12339 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=helloWorldStep, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=

 調査したところ、JobInstanceはジョブ名とジョブパラメータ毎に生成され、引数なしの再実行は1回目と同様のJobInstanceと見なされた様です。そして、1回目はすでにステータスがCOMPLETEDになっている為、該当のログが出力されました。
 そこで、mvn spring-boot:run -Dspring-boot.run.arguments="name=Yamada"と、コマンドライン引数を与えて実行したことろ、以下の通り無事実行することができました。

...
2020-08-01 17:36:21.093  INFO 14877 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [helloWorldStep]
Hello: Yamada
2020-08-01 17:36:21.134  INFO 14877 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [helloWorldStep] executed in 40ms
...

実行後のDB

 再度、batch_job_instanceテーブルとbatch_job_executionテーブルの中身を確認してみましょう。結果、以下の通り2回分の実行が記録されています。

postgres=# select * from batch_job_instance;
 job_instance_id | version |   job_name    |             job_key
-----------------+---------+---------------+----------------------------------
               1 |       0 | helloWorldJob | d41d8cd98f00b204e9800998ecf8427e
               2 |       0 | helloWorldJob | e44a461448a1f43e3c7fcd84c6f17fe3
(2 rows)

postgres=# select * from batch_step_execution;
 step_execution_id | version |   step_name    | job_execution_id |       start_time        |        end_time         |  status   | commit_count | read_count | filter_count | write_count | read_skip_count | write_skip_count | process_skip_count | rollback_count | exit_code | exit_message |      last_updated
-------------------+---------+----------------+------------------+-------------------------+-------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+-------------------------
                 1 |       3 | helloWorldStep |                1 | 2020-07-30 08:36:10.486 | 2020-07-30 08:36:10.528 | COMPLETED |            1 |          0 |            0 |           0 |               0 |                0 |0 |              0 | COMPLETED |              | 2020-07-30 08:36:10.528
                 2 |       3 | helloWorldStep |                3 | 2020-07-31 08:03:39.362 | 2020-07-31 08:03:39.407 | COMPLETED |            1 |          0 |            0 |           0 |               0 |                0 |0 |              0 | COMPLETED |              | 2020-07-31 08:03:39.407
(2 rows)

*1:https://fujiu.hatenablog.com/entry/2020/06/09/201255を合わせてご参照ください。