バッチサービスの作成

このガイドでは、基本的なバッチ駆動型ソリューションの作成プロセスについて説明します。

構築するもの

CSV スプレッドシートからデータをインポートし、それをカスタムコードで変換し、データベースに最終結果を保存するサービスを構築します。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-batch-processing/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックし、Spring BatchHyperSQL データベースを選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。

ビジネスデータ

通常、顧客またはビジネスアナリストがスプレッドシートを提供します。この簡単な例では、src/main/resources/sample-data.csv で作成されたデータを見つけることができます。

Jill,Doe
Joe,Doe
Justin,Doe
Jane,Doe
John,Doe

このスプレッドシートには、各行にコンマで区切られた名と姓が含まれています。これは、Spring がカスタマイズなしで処理できるかなり一般的なパターンです。

次に、データを保存するテーブルを作成する SQL スクリプトを作成する必要があります。このようなスクリプトは src/main/resources/schema-all.sql にあります。

DROP TABLE people IF EXISTS;

CREATE TABLE people  (
    person_id BIGINT IDENTITY NOT NULL PRIMARY KEY,
    first_name VARCHAR(20),
    last_name VARCHAR(20)
);
Spring Boot は、起動時に schema-@@platform@@.sql を自動的に実行します。-all は、すべてのプラットフォームのデフォルトです。

ビジネスクラスを作成する

データの入力および出力の形式を確認できるようになったため、次の例(src/main/java/com/example/batchprocessing/Person.java から)が示すように、データの行を表すコードを作成できます。

package com.example.batchprocessing;

public record Person(String firstName, String lastName) {

}

コンストラクターを使用して、姓と名を使用して Person レコードをインスタンス化できます。

中間プロセッサーを作成する

バッチ処理の一般的なパラダイムは、データを取り込み、変換し、別の場所にパイプで送ることです。ここでは、名前を大文字に変換する簡単なトランスフォーマーを作成する必要があります。次のリスト(src/main/java/com/example/batchprocessing/PersonItemProcessor.java から)は、その方法を示しています。

package com.example.batchprocessing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.batch.item.ItemProcessor;

public class PersonItemProcessor implements ItemProcessor<Person, Person> {

  private static final Logger log = LoggerFactory.getLogger(PersonItemProcessor.class);

  @Override
  public Person process(final Person person) {
    final String firstName = person.firstName().toUpperCase();
    final String lastName = person.lastName().toUpperCase();

    final Person transformedPerson = new Person(firstName, lastName);

    log.info("Converting (" + person + ") into (" + transformedPerson + ")");

    return transformedPerson;
  }

}

PersonItemProcessor は、Spring Batch の ItemProcessor インターフェースを実装しています。これにより、このガイドの後半で定義するバッチジョブにコードを簡単に接続できます。インターフェースに従って、受信 Person オブジェクトを受け取り、その後、それを大文字の Person に変換します。

入力と出力の型は同じである必要はありません。実際、1 つのデータソースを読み取った後、アプリケーションのデータフローに異なるデータ型が必要になる場合があります。

バッチジョブをまとめる

ここで、実際のバッチジョブをまとめる必要があります。Spring Batch は、カスタムコードを記述する必要性を減らす多くのユーティリティクラスを提供します。代わりに、ビジネスロジックに集中できます。

ジョブを構成するには、最初に src/main/java/com/example/batchprocessing/BatchConfiguration.java の次の例のように Spring @Configuration クラスを作成する必要があります。この例では、メモリベースのデータベースを使用しています。つまり、データベースが完了すると、データは失われます。次の Bean を BatchConfiguration クラスに追加して、リーダー、プロセッサー、ライターを定義します。

@Bean
public FlatFileItemReader<Person> reader() {
  return new FlatFileItemReaderBuilder<Person>()
    .name("personItemReader")
    .resource(new ClassPathResource("sample-data.csv"))
    .delimited()
    .names("firstName", "lastName")
    .targetType(Person.class)
    .build();
}

@Bean
public PersonItemProcessor processor() {
  return new PersonItemProcessor();
}

@Bean
public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
  return new JdbcBatchItemWriterBuilder<Person>()
    .sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
    .dataSource(dataSource)
    .beanMapped()
    .build();
}

コードの最初のチャンクは、入力、プロセッサー、出力を定義します。

  • reader() は ItemReader を作成します。sample-data.csv と呼ばれるファイルを探し、各項目を十分な情報で解析して Person に変換します。

  • processor() は、前に定義した PersonItemProcessor のインスタンスを作成します。これは、データを大文字に変換するためのものです。

  • writer(DataSource) は ItemWriter を作成します。これは JDBC 宛先を対象としており、Spring Boot によって作成された dataSource のコピーを自動的に取得します。これには、Java レコードコンポーネントによって駆動される、単一の Person を挿入するために必要な SQL ステートメントが含まれています。

最後のチャンク(src/main/java/com/example/batchprocessing/BatchConfiguration.java から)は、実際のジョブ構成を示しています。

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

@Bean
public Step step1(JobRepository jobRepository, DataSourceTransactionManager transactionManager,
          FlatFileItemReader<Person> reader, PersonItemProcessor processor, JdbcBatchItemWriter<Person> writer) {
  return new StepBuilder("step1", jobRepository)
    .<Person, Person> chunk(3, transactionManager)
    .reader(reader)
    .processor(processor)
    .writer(writer)
    .build();
}

最初のメソッドはジョブを定義し、2 番目のメソッドは単一のステップを定義します。ジョブはステップから構築されます。各ステップには、リーダー、プロセッサー、ライターが含まれます。

次に、各ステップをリストします (ただし、このジョブにはステップが 1 つしかありません)。ジョブが終了し、Java API は完全に構成されたジョブを生成します。

ステップ定義では、一度に書き込むデータの量を定義します。この場合、一度に最大 3 つのレコードが書き込まれます。次に、前に注入した Bean を使用して、リーダー、プロセッサー、ライターを構成します。

chunk() は汎用メソッドであるため、<Person,Person> の接頭辞が付きます。これは、処理の各「チャンク」の入力および出力型を表し、ItemReader<Person> および ItemWriter<Person> と並んでいます。

バッチ構成の最後の部分は、ジョブが完了したときに通知を受ける方法です。次の例(src/main/java/com/example/batchprocessing/JobCompletionNotificationListener.java から)は、そのようなクラスを示しています。

package com.example.batchprocessing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.jdbc.core.DataClassRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class JobCompletionNotificationListener implements JobExecutionListener {

  private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);

  private final JdbcTemplate jdbcTemplate;

  public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  @Override
  public void afterJob(JobExecution jobExecution) {
    if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
      log.info("!!! JOB FINISHED! Time to verify the results");

      jdbcTemplate
          .query("SELECT first_name, last_name FROM people", new DataClassRowMapper<>(Person.class))
          .forEach(person -> log.info("Found <{{}}> in the database.", person));
    }
  }
}

JobCompletionNotificationListener は、ジョブが BatchStatus.COMPLETED の場合にリッスンし、JdbcTemplate を使用して結果をインスペクションします。

アプリケーションを実行可能にする

バッチ処理は Web アプリと WAR ファイルに埋め込むことができますが、以下に示すより単純なアプローチはスタンドアロンアプリケーションを作成します。古き良き Java main() メソッドによって駆動される単一の実行可能な JAR ファイルにすべてをパッケージ化します。

Spring Initializr がアプリケーションクラスを作成しました。この単純な例の場合、それをさらに変更しなくても機能します。次のリスト(src/main/java/com/example/batchprocessing/BatchProcessingApplication.java から)は、アプリケーションクラスを示しています。

package com.example.batchprocessing;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BatchProcessingApplication {

  public static void main(String[] args) {
    System.exit(SpringApplication.exit(SpringApplication.run(BatchProcessingApplication.class, args)));
  }
}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

SpringApplication.exit() および System.exit() は、ジョブの補完時に JVM が確実に終了することに注意してください。詳細については、Spring Boot リファレンスドキュメントのアプリケーション終了セクションを参照してください。

デモンストレーションのために、JdbcTemplate を作成し、データベースを照会し、バッチジョブが挿入する人の名前を出力するコードがあります。

アプリケーションが @EnableBatchProcessing アノテーションを使用していないことに注意してください。以前は、@EnableBatchProcessing を使用して Spring Boot の Spring Batch の自動構成を有効にすることができました。Spring Boot v3.0 以降、このアノテーションは不要になったため、Spring Boot の自動構成を使用するアプリケーションから削除する必要があります。@EnableBatchProcessing のアノテーションが付けられた Bean または Spring Batch の DefaultBatchConfiguration を継承した Bean を定義して、自動構成をバックオフするように指示できるようになり、アプリケーションが Spring Batch の構成方法を完全に制御できるようになります。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-batch-processing-0.1.0.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-batch-processing-0.1.0.jar
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。

ジョブは、変換された各人の行を出力します。ジョブの実行後、データベースのクエリからの出力も確認できます。次の出力のようになります。

Converting (Person[firstName=Jill, lastName=Doe]) into (Person[firstName=JILL, lastName=DOE])
Converting (Person[firstName=Joe, lastName=Doe]) into (Person[firstName=JOE, lastName=DOE])
Converting (Person[firstName=Justin, lastName=Doe]) into (Person[firstName=JUSTIN, lastName=DOE])
Converting (Person[firstName=Jane, lastName=Doe]) into (Person[firstName=JANE, lastName=DOE])
Converting (Person[firstName=John, lastName=Doe]) into (Person[firstName=JOHN, lastName=DOE])
Found <{Person[firstName=JILL, lastName=DOE]}> in the database.
Found <{Person[firstName=JOE, lastName=DOE]}> in the database.
Found <{Person[firstName=JUSTIN, lastName=DOE]}> in the database.
Found <{Person[firstName=JANE, lastName=DOE]}> in the database.
Found <{Person[firstName=JOHN, lastName=DOE]}> in the database.

要約

おめでとう! スプレッドシートからデータを取り込んで処理し、データベースに書き込むバッチジョブを作成しました。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。

コードを入手する

プロジェクト