バッチ処理とSpring Batchの概要 | 第2回 TaskletモデルでHello Worldを出力するバッチ処理を実装する

目次

  1. はじめに
  2. プロジェクトの作成
  3. 設計
  4. 実装
  5. 作成したバッチ処理を実行する
  6. おわりに

1. はじめに

この記事は初めてバッチ処理のタスクを振られたエンジニアがバッチ処理の概要を理解して, Spring Batchで基本的なバッチ処理を実装できるようになる事を目標にしています。

記事は以下の通りの内容で何回かに分けて投稿します

第一回の記事で紹介した通り, Spring Batchの典型的な流れは、JobLauncherによってJobが開始され、Jobによって複数のStepが実行され、Step内ではデータの読み込み処理・加工処理・書き込み処理がなされるといった感じです(図1)。

図1. Spring Batchの典型例([6]のFigure 1. Batch Stereotypesより作成)

この図1のStepの実装方法には2つのモデルがあり、図1のようにデータ読み込みクラス・加工クラス・書き込みクラスを分けるモデルをChunkモデルと呼びます。一方で、データの書き込み処理は必要ない場合などはTaskletという1つのクラス内にデータを扱う処理を実装することがあります。これをTaskletモデルと言います。

まずはTaskletモデルでHello Worldを出力するバッチ処理を実装してみましょう。

ゴールはバッチ処理を実行したら、写真1のようにコンソールに「Hello World」という文字が出力されるようにすることです。

写真1. Hello Worldがログ出力されている様子

2. プロジェクトの作成

それではプロジェクトを作っていきましょう。

今回はSpring Initializrを使ってSpringプロジェクトの雛形を作っていきます(写真2)。

このリンクから写真2の設定のSpring Initializrにアクセスできます。

写真2. Spring Initializrの設定画面

問題がなければ、Generateを押してプロジェクトのzipファイルをダウンロードして、任意のディレクトリで解凍してIntteli J IDEAなどのIDEで開いてください(EclipseでもOK)。

注意点ですが、今回は筆者のプロジェクトで使っているバージョンを利用するために、Spring Batchのバージョンが4.x系、Javaが8を使っています。

中には最新バージョンのSpring Boot・Spring Batchではコンパイルが通らないことがありますので、Spring Batchを5.x系でやりたい場合は適宜読み替えながらやってください。

3. 設計

簡単に設計を見ていきましょう。

今回は主に2つのクラスを実装していきます。

  1. Taskletのクラス(tasklet/HelloTasklet.java)
  2. バッチ処理の設定クラス(config/BatchConfig.java)

1番のクラスでは、Taskletの具体的な処理を記述していきます。 ここでHello Worldをログに出力する処理を記述します。

2番のクラスでは、バッチ処理の設定を行うために、どのJobがどのStepを呼び出しており、StepがどのTaskletを呼び出しているか?という内容を記述していきます。ここでStepから1番のTaskletを呼び出すことで、このStepを呼び出しているJobクラスを実行することでバッチ処理を実行してログにHello Worldと出力できます。

4. 実装

それでは具体的な実装を行っていきましょう。

4.1. Taskletクラスの実装

まずは1番のTaskletのクラスを実装していきます。

ターミナルを開いてBatchHelloWorldTasketディレクトリに移動して、以下の通りにtaskletというディレクトリとHelloTasklet.javaというファイルを作成しましょう。 mkdirから末尾までで1コマンド、touchから末尾までで1コマンドです。

➜  BatchHelloWorldTasklet $ mkdir src/main/java/com/example/BatchHelloWorldTasklet/tasklet
➜  BatchHelloWorldTasklet $ touch src/main/java/com/example/BatchHelloWorldTasklet/tasklet/HelloTasklet.java

HelloTasklet.javaを開いて以下の通りに記述してください。

package com.example.BatchHelloWorldTasklet.tasklet;

import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

// (2). Taskletは@ComponentをつけてBeanとしてDIコンテナに登録する必要がある
@Component
// (3). インスタンスの生存期間をStepの開始から終了までに指定
@StepScope
// log出力を行うために使用する
@Slf4j
// (1). Taskletインタフェースを実装すればTaskletの処理を作れる
public class HelloTasklet implements Tasklet {

    // (4). TaskletインタフェースのexecuteメソッドがStepで実行される。
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        // (5). ログを出力する
        log.info("Hello World");
        // (6). Taskletを終了する繰り返しステータスを返す
        return RepeatStatus.FINISHED;
    }
}

要点を絞って(1)~(5)まで1つずつ解説していきます。

まずTaskletを作る際は、(1)のようにTaskletの処理を行うクラスにTaskletインタフェースを実装するように設定します。

// (1). Taskletインタフェースを実装すればTaskletの処理を作れる
public class HelloTasklet implements Tasklet {
  略
}

そしてTaskletを使用できるようにするために、(2)のように@Componentアノテーションをクラスに付けて、TaskletをBeanとしてDIコンテナに登録されるように設定します。

// (2). Taskletは@ComponentをつけてBeanとしてDIコンテナに登録する必要がある
@Component
public class HelloTasklet implements Tasklet {
  略
}

次に、Taskletのインスタンスの生存期間をStep内にするために、@StepScopeアノテーションを付けます。

@Component
// (3). インスタンスの生存期間をStepの開始から終了までに指定
@StepScope
public class HelloTasklet implements Tasklet {
}

次に、(4)番でStepによってTaskletが呼び出された際に実行される具体的な処理をexecuteというメソッド使って書いていきます。executeメソッドはTaskletインタフェースで定義されているメソッドであり、それの具体的な処理をここで記述します。

そして、(5)番でHello Worldとログに出力する処理を記述します。 最後に、(6)番でTaskletの繰り返しステータスとしてRepeatStatus.FINISHEDをStepに返すことで、このTaskletを繰り返さないことをStepに伝えます。

public class HelloTasklet implements Tasklet {

    // (4). TaskletインタフェースのexecuteメソッドがStepで実行される。
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        // (5). ログを出力する
        log.info("Hello World");
        // (6).  Taskletを終了する繰り返しステータスを返す
        return RepeatStatus.FINISHED;
    }
}

4.2 BatchConfigクラスの実装

Taskletの実装が終わったので、次はTaskletを呼び出すStep、Stepを呼び出すJobを記述するバッチ処理の設定クラスを作成していきます。

まずは、以下の通りパッケージとクラスを作成します。

➜  BatchHelloWorldTasklet $ mkdir src/main/java/com/example/BatchHelloWorldTasklet/config
➜  BatchHelloWorldTasklet $ touch src/main/java/com/example/BatchHelloWorldTasklet/config/BatchConfig.java

それでは、BatchHelloWorldTasklet.javaを開いてStepとJobを記述してバッチ処理を完成させましょう。

package com.example.BatchHelloWorldTasklet.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// (2). 各クラスのインスタンスをBeanに登録するためのアノテーション
// @Beanアノテーションを使ってBeanを登録できる
@Configuration
// (1). @EnableBatchProcessingをつけることでSpringバッチの設定が可能となる
// Job生成用のクラス(JobBuilderFactory), StepBuilderFactory(Step生成用クラス)
// などのクラスのインスタンスをDIできるようになる
@EnableBatchProcessing
public class BatchConfig {

    // (1.1). JobBuilderのFactoryクラス
    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    // (1.2). StepBuilderのFactoryクラス
    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    // (3). Stepから呼び出すTaskletをフィールドに設定する
    @Autowired
    private Tasklet helloTasklet;

    // (4). Jobから呼び出すStepを定義する
    @Bean
    public Step taskletStep1() {
        // StepBuilderFactoryを使ってStepを作成
        return stepBuilderFactory.get("HelloTaskletStep1") // Builderの取得
                .tasklet(helloTasklet) // Stepで実行するTaskletをセット
                .build(); // Stepを作成する
    }

    // (5). バッチとして実行するJobを定義する
    // @Beanをつけることで@ConfigurationをつけたこのクラスにBeanとしてJobを登録する
    @Bean
    public Job taskletJob() throws Exception {
        // (5.1). JobBuilderFactoryを使ってJobを作成する
        return jobBuilderFactory.get("HelloWorldTaskletJob") // Jobの名前を設定する
                .incrementer(new RunIdIncrementer()) // JobのIDをインクリメントさせるクラスを指定
                .start(taskletStep1()) // 最初に実行するStepを指定する
                .build(); // Jobを作成する
    }
}

まずは(1)のようにクラスに@EnableBatchProcessingというアノテーションを付けることで、Springバッチの設定ができるようにしましょう。 具体的にはこのアノテーションを付けることでJob生成クラス(1.1)やStep生成クラス(1.2.)などのバッチ処理の設定に欠かせないクラスのインスタンスをDIできるようになります。

次に(2)のように@Configurationというアノテーションを付けて、各クラスのインスタンスをBeanに登録できるようにしましょう。このアノテーションをクラスにつけることで、クラス内の@Beanを付けたものがBeanとして登録されます。

次に(3)のように先程作成したTaskletをフィールドとして設定しましょう。また@Autowiredをつけることで自動的にインスタンス化されるようにしておきます。

次に(4)のようにJobから呼び出されるStepを定義して、Stepで実行するTaskletをセットしてStepを作成するように実装します。

そして最後に(5)でバッチとして実行するJobを定義して、Jobの名前や最初に実行するStepを指定して最後にビルドメソッドを呼び出してJobを作成しましょう。

これでバッチ処理の実装は終了です。

5. 作成したバッチ処理を実行する

作成したバッチ処理を実行してみましょう。

Inttelli J IDEAではBatchHelloWorldTaskletApplication.javaを開いて、右上の矢印マークから実行を押しましょう。

写真3. バッチの実行

上手く実行されれば、写真4のようにHello Worldが表示されます。

写真4. 成功した場合はHello Worldが表示される

6. 最後に

今回はバッチ処理の基本機能を理解するためにTaskletモデルでHello Worldをコンソールに表示する簡単なプログラムを作成しました。

次回は一歩進んで、CSVを読み込んで加工して結果を別のCSVに書き込むバッチ処理をChunkモデルを使って実装していきたいと思います。

参考文献

バッチ処理とSpring Batchの概要 | 第1回 バッチ処理とSpring Batchの概念を理解する

目次

  1. はじめに
  2. バッチ処理とは?
  3. Spring Batchとは?
  4. Spring Batchの典型例
  5. Stepの2つのモデル
  6. 最後に

1. はじめに

この記事は初めてバッチ処理のタスクを振られたエンジニアがバッチ処理の概要を理解して, Spring Batchで基本的なバッチ処理を実装できるようになる事を目標にしています。

記事は以下の通りの内容で何回かに分けて投稿します

2. バッチ処理とは?

バッチ処理とは、処理したいデータを一定期間溜め込んでから一括処理を行う方式で、データの読み書きを逐次実行するリアルタイム処理に比べて効率的にデータの書き込みができることができます[1]。

またバッチ処理の歴史は古く, 最初の事例としては1890年に米国国勢調査局の情報を記録するためにパンチカードを用いてバッチ処理を用いたことが挙げられます[2]。

バッチ処理の一般的な使用例としては、夜間に当日の売上をバッチ処理を用いて集計することがあります。

このように、売上のデータを一括で集計することで、売上があがるたびにデータを集計する場合に比べて、読み書きのオーバーヘッドを減らし効率的に処理することが可能です。

またバッチ処理の他の例としては、給与の計算、在庫処理、レポート作成などが挙げられます。

3. Spring Batchとは?

3.1. Spring Batchの概要

Spring Batchとは, Javaによるバッチアプリケーションを効率的に開発するために作られたバッチフレームワークであり,Springの機能を利用することができるため, DI・バリデーション・DBアクセスなどの機能を簡単に使用できることが特徴です[3]。

3.2. Spring Batchのバージョンに関して

なお現在のSpring Batchの最新バージョンは5.xですが、本記事では4.x系のバージョンを前提として説明を行います[4]。

理由としては, 筆者が関与しているプロダクトのSpring Batchのバージョンが4.x系であること, Spring Batchに関する参考文献が4.x系で書かれていることなどです。

また4.xを使用するに際して、最新のSpring BootではDeprecated(非推奨)となっているメソッドを使用することがありますが、その際に最新のSpring Bootではビルドができない場合があるので、適宜バージョンを確認しながら進めてください[5]。

4. Spring Batchの典型例

Spring Batchは主にJobLauncher, Job, Step, JobRepositoryの4つの要素で構成されます(図1)[6]。

図1. Spring Batchの典型例([6]のFigure 1. Batch Stereotypesより作成)

実行時の処理の流れとしては、まずJobLauncherによってバッチ処理全体を表すJobが起動され、Jobによって複数のStepが実行され、Step内でデータの読み込み処理(ItemReader)・加工処理(ItemProcessor)・書き込み処理(ItemWriter)が実施されます。また, バッチの実行中で得た処理件数・ステータスなどのメタデータに関してはJobRepositoryに保存されます。

5. Stepの2つのモデル

Spring BatchのStepにはChunkモデルとTaskletモデルの2つのモデルが存在します。

Chunkモデルとは, データの読み込み処理(ItemReader)・加工処理(ItemProcessor)・書き込み処理(ItemWriter)というバッチ処理の定型処理を作るためのモデルで先程の図1で示したモデルはこのChunkモデルに該当すると思われます(表1)。

一方で, バッチ処理を作る際にデータの書き込みを行わない, より自由にstep記述したい場合ではTaskletモデルが使われます(表1)。

Taskletモデルの場合は, executeメソッド内に読み込み・加工・書き込み処理を自由に記述して実行することでstepが実行されます。

実装のカスタマイズ性 複雑さ
Chunk 低い. ItemReader, ItemProcessor, ItemWriterに沿って 記述しなければならないため シンプル. 読み込み・加工 ・書き込みの定型フロー に沿えばよいため、 複雑性は低い.
Tasklet 高い. Taskletインタフェース のみ満たすクラスを実装すれば いいので自由度が高い. Taskletを自由に書けるため、 プログラムが大きくなると 複雑になりやすい

表1. ChunkモデルとTaskletモデルの特徴

6. 最後に

第1回ではバッチ処理の歴史からSpring Batchの典型的な実装パターンまでを概観しました。 第2回ではこのTaskletモデルに沿って簡単なバッチ処理を実装することを通して、Spring Batchの基本機能について解説します。

参考

【keg-only】Homebrewでパッケージ(keg)をインストールしたのに「command not found」と表示される原因

はじめに

皆さんはhomebrewで何かしらのパッケージをインストールしたことはありますか?インストールしたら直ぐにコマンドが使える時もあれば、インストールしたのに「command not found」と表示されてしまい直ぐに使えないこともあると思います。

少し開発をやっていた人であれば、「パッケージをインストールしたのにcommand not foundと表示されるということはパスが通ってないのでは?パスの通し方調べるかぁ〜」と思うでしょう。

しかしながら、なぜbrewでインストールしたのにパスが通っている時と通っていない時があるのでしょうか?

様々な理由があると思いますが、その一因として「keg-only」というhomebrewの仕様があります。

本記事ではkeg-onlyについて解説したいと思います。

keg-onlyとは?

homebrewの公式のFAQによると, keg-onlyとはパッケージ(keg)のみがインストールされ、インストール先にデフォルトでリンクが貼られないインストール方法のことを指していることがわかります[1]。

何でkeg-onlyのパッケージがあるのか?

パッケージをインストールする側にとっては「シンボリックリンクを貼っといてくれよ、、、」と感じてしまいますが、HomebrewのGitHubリポジトリのQ&Aには「keg-onlyかどうかをインストーラ側に強制的に指定することはあまりよろしくない」という旨のコメントがあります[2]。

Only you can decide whether you need to override the fact that a package is keg-only. If you're not sure, it's probably best not to.

それでは、なぜkeg-onlyという仕様が必要なのでしょうか?

1つ考えられる理由は, 最新バージョンのパッケージがインストールされている状態で、brewから古いバージョンの同一パッケージをインストールした際に、既存のパッケージのシンボリックリンクを上書きしないようにしてくれていることが挙げられます[3]。

keg-onlyかどうかはどうやって調べるのか?

このkeg-onlyかどうかについては、インストール時の最後に表示される文章を読めばわかります。

例えば、mysql@5.7をインストールした例では以下のように「mysql@5.7はkeg-onlyなので、/usr/localにsymlinkedされていません」と出力されます。

その直ぐ下に「mysql@5.7をPATH内で実行する場合は、echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.bash_profile を実行してください。」とあるのがわかると思います。

これに従ってパッケージのリンクをPATHに通してあげることで解決できます。

mysql@5.7 is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have mysql@5.7 first in your PATH run:
  echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.bash_profile

For compilers to find mysql@5.7 you may need to set:
  export LDFLAGS="-L/usr/local/opt/mysql@5.7/lib"
  export CPPFLAGS="-I/usr/local/opt/mysql@5.7/include"

最後に

今回は, homebrewのkeg-onlyという仕様について解説しました。

なんとなく対処法を分かっている人は多いと思うものの、どういった時にPATHが通らないか?というインストーラの事情を知ることでより普段使うツールへの理解が深まるのではないでしょうか。

参考

  • [1] FAQ (Frequently Asked Questions) — Homebrew Documentation

  • [2] What is keg-only? · Homebrew · Discussion #239 · GitHub

  • [3] keg-onlyとは

  • [4] MySQL5.7をHomebrewでmacOSにインストールする手順