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

構築するもの

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

必要なもの

このガイドを完了する方法

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

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

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

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

ビジネスデータ

通常、顧客またはビジネスアナリストがスプレッドシートを提供します。この簡単な例では、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は、起動時に [email protected](英語) @[email protected](英語) @.sql を自動的に実行します。 -all は、すべてのプラットフォームのデフォルトです。

Spring Initializrから開始

すべてのSpringアプリケーションの場合、Spring Initializr(英語) から開始する必要があります。Initializrは、アプリケーションに必要なすべての依存関係をすばやく取り込む方法を提供し、多くのセットアップを行います。この例では、Spring BatchおよびHyperSQLデータベースの依存関係が必要です。次のイメージは、このサンプルプロジェクト用に設定されたInitializrを示しています。

initializr
前の図は、Mavenがビルドツールとして選択されたInitializrを示しています。Gradleも使用できます。また、com.example および batch-processing の値をそれぞれグループおよびアーティファクトとして表示します。このサンプルの残りの部分では、これらの値を使用します。

次のリストは、Mavenを選択したときに作成される pom.xml ファイルを示しています。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>batch-processing</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>batch-processing</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

次のリストは、Gradleを選択したときに作成される build.gradle ファイルを示しています。

plugins {
	id 'org.springframework.boot' version '2.2.2.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-batch'
	runtimeOnly 'org.hsqldb:hsqldb'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	testImplementation 'org.springframework.batch:spring-batch-test'
}

test {
	useJUnitPlatform()
}

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

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

package com.example.batchprocessing;

public class Person {

  private String lastName;
  private String firstName;

  public Person() {
  }

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return "firstName: " + firstName + ", lastName: " + 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) throws Exception {
    final String firstName = person.getFirstName().toUpperCase();
    final String lastName = person.getLastName().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/exampe/batchprocessing/BatchConfiguration.javaの次の例のように、最初にSpring @Configuration クラスを作成する必要があります。

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

  @Autowired
  public JobBuilderFactory jobBuilderFactory;

  @Autowired
  public StepBuilderFactory stepBuilderFactory;

    ...

}

手始めに、@EnableBatchProcessing アノテーションは、ジョブをサポートし、多くの脚の作業を節約する多くの重要なBeanを追加します。この例では、メモリベースのデータベース( @EnableBatchProcessingが提供)を使用しています。つまり、完了するとデータが失われます。また、さらに必要ないくつかのファクトリーをオートワイヤーします。次に、BatchConfiguration クラスに次のBeanを追加して、リーダー、プロセッサ、およびライターを定義します。

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

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

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

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

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

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

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

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

@Bean
public Job importUserJob(JobCompletionNotificationListener listener, Step step1) {
  return jobBuilderFactory.get("importUserJob")
    .incrementer(new RunIdIncrementer())
    .listener(listener)
    .flow(step1)
    .end()
    .build();
}

@Bean
public Step step1(JdbcBatchItemWriter<Person> writer) {
  return stepBuilderFactory.get("step1")
    .<Person, Person> chunk(10)
    .reader(reader())
    .processor(processor())
    .writer(writer)
    .build();
}

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

このジョブ定義では、ジョブがデータベースを使用して実行状態を維持するため、インクリメンターが必要です。次に、各ステップをリストします(ただし、このジョブには1つのステップしかありません)。ジョブは終了し、Java APIは完全に構成されたジョブを生成します。

ステップ定義では、一度に書き込むデータ量を定義します。この場合、一度に最大10個のレコードを書き込みます。次に、先ほど注入したビットを使用して、リーダー、プロセッサー、およびライターを構成します。

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.listener.JobExecutionListenerSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {

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

  private final JdbcTemplate jdbcTemplate;

  @Autowired
  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",
        (rs, row) -> new Person(
          rs.getString(1),
          rs.getString(2))
      ).forEach(person -> log.info("Found <" + person + "> in the database."));
    }
  }
}

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) throws Exception {
    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であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

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

実行可能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 (firstName: Jill, lastName: Doe) into (firstName: JILL, lastName: DOE)
Converting (firstName: Joe, lastName: Doe) into (firstName: JOE, lastName: DOE)
Converting (firstName: Justin, lastName: Doe) into (firstName: JUSTIN, lastName: DOE)
Converting (firstName: Jane, lastName: Doe) into (firstName: JANE, lastName: DOE)
Converting (firstName: John, lastName: Doe) into (firstName: JOHN, lastName: DOE)
Found <firstName: JILL, lastName: DOE> in the database.
Found <firstName: JOE, lastName: DOE> in the database.
Found <firstName: JUSTIN, lastName: DOE> in the database.
Found <firstName: JANE, lastName: DOE> in the database.
Found <firstName: JOHN, lastName: DOE> in the database.

要約

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

関連事項

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

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

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