実際に書いてみる!Javaによるバッチ処理とは【初心者向け】

初心者向けにJavaによるバッチ処理について解説しています。バッチ処理は一定の量のデータを一括で処理するために用いられます。ここではSpring Batchフレームワークを使った方法を紹介します。バッチ処理の概念と流れについて学習しましょう。

TechAcademyマガジンはオンラインのプログラミングスクールTechAcademy [テックアカデミー]が運営する教育×テクノロジーのWebメディアです。初心者でもすぐ勉強できる記事が2,000以上あります。

この記事では、Javaによるバッチ処理について解説します。

実際にプログラムを書いて説明しているので、ぜひ理解しておきましょう。

 

なお本記事は、TechAcademyのJava講座の内容をもとに作成しています。

 

田島悠介

今回は、Javaに関する内容だね!

大石ゆかり

どういう内容でしょうか?

田島悠介

バッチ処理について詳しく説明していくね!

大石ゆかり

お願いします!

 

バッチ処理とは

例えば、お店で働いていて倉庫から必要な品物を取りに行く作業をしているとします。

この作業を下記2つの方法で行ったときの、それぞれの場合について考えてみます。

  1. 必要な品物が分かったらその都度1個づつ取りに行く
  2. 必要な品物が分かったらメモしておいて、最後にまとめて取りに行く

1の場合、利点は要求にすぐに応えられますが、倉庫に何度も行ったり来たりするのは非効率です。
対して、2は作業実施まで時間を置きますが、実際の作業は1より効率的に行えます。

この2の方法がバッチ処理と言われるものの大まかなイメージです。
決められたタイミングで処理を一括して行うことがその特徴です。もちろん、この方法が適さない場合もあります。

上の例で言えば、品物を店前でお客が待っている様な場合はこの方法は取れません。そう言った即時性の求められる作業にバッチ処理は適しません。

実際のシステムでバッチ処理を利用する場面としてはDBへ大量のデータを書込む処理等が挙げられます。この場合、他の即時処理と同時に行わないことで一度にかかるシステム全体の負荷を下げることができます。

さらに、システムの場合は作業の主体がプログラムなので、実行後は人の手を必要としないという利点も加わります。

 

Javaによるバッチ処理の例

Javaでバッチ処理を行う方法として、ここでは「Spring Batch」というフレームワークを紹介します。このフレームワークの機能を当欄で全て解説するのは難しいため、最低限の動作を行うために必要な手順に絞って説明します。

まず、「Spring Batch」を利用するための環境を整えます。

  1. Eclipseを起動し、「ヘルプ(H)」>「Eclipse マーケットプレース(M)…」を選択。
  2. 「検索(I):」で「STS」と入力、「Spring ツール」が表示されるので「インストール」をクリック。
  3. インストールを続行するか確認されるので「確認」をクリック。
  4. インストール処理後、「使用条件の条項に同意します(A)」にチェックし「完了(F)」をクリック。
    ※インストール続行について警告が出た場合は同意して続行する
  5. Eclipseの再起動を促されるので、再起動する。
    ※再起動後は「Spring Framework」の「ようこそ」というタブが開くがこれは閉じて良い。ここまでの手順で「Spring Batch」を利用できる環境が整います。続いて、確認の為にサンプルプロジェクトを作成して実行します。
  6. 「ファイル」→「新規」→「プロジェクト」→「Spring」→「Spring レガシー・プロジェクト」を選択、「次へ」
  7. 任意のプロジェクト名を入力、テンプレートに「Simple SpringBatch Project」を選択、「次へ」
    ※初回はダウンロード続行について警告が出るので同意して続行する
  8. 任意の「トップレベルパッケージ名」を付けて「完了」
    ※ここまでの手順でパッケージ・エクスプローラーに作成したプロジェクトが表示されます。
  9. プロジェクトを右クリックして、[実行]→[実行の構成…]をクリック。
  10. 「構成の作成、管理、および実行」画面で、「Javaアプリケーション」を選択し、「新規の起動構成」をクリック。
  11. 任意の名前を入力して、メイン・クラスへ「org.springframework.batch.core.launch.support.CommandLineJobRunner」を入力。
  12. 引数タブを選択して、「プログラム引数」に「classpath:/launch-context.xml job1」を入力
  13. 「実行」ボタンをクリック
    実行後はコンソールにログが大量に出ます。
    ログの中に「<[Hello world!]>」を含む行が有ればサンプルバッチの実行は成功です。

メインクラスとして設定したCommandLineJobRunnerクラスは下記の引数を渡すことができます。

  • 引数1:Spring設定ファイル(XML)のパス
  • 引数2:バッチ処理の対象作業(=ジョブ)の名前
  • 引数3:ジョブに渡すパラメタ(省略可)

サンプルプロジェクトでいうと、「classpath:/launch-context.xml」がSpring設定ファイルのパスで、「job1」というのがジョブ名です。

この「job1」というジョブの設定は「module-context.xml」の中にあります。

このxmlの中に「<batch:job id=”job1″>」というタグがあり、このタグで囲んだ中にジョブの設定が書かれています。

(例)サンプルプロジェクトのmodule-context.xmlの内容

<batch:job id="job1">
<batch:step id="step1" > ←1
<batch:tasklet transaction-manager="transactionManager" start-limit="100" > ←2
<batch:chunk reader="reader" writer="writer" commit-interval="1" /> ←3
</batch:tasklet>
</batch:step>
</batch:job>

上記の通りサンプルのジョブは「step(上記1)」、「tasklet(上記2)」、「chunk(上記3)」という単位で構成されています。このうち上記③の行の「reader=”reader”」と「writer=”writer”」という部分に注目してください。

「chunk」タグで定義される処理は基本的に「データ読込」、「データ整形」、「データ出力」の3つから構成されます。それら3つの処理を行うクラスを指定するのが「chunk」タグの「reader」、「processor」、「writer」の属性です。

サンプルプロジェクトの場合、「reader」と「writer」は下記クラスと対応しています。

----------------------------
@Component("reader")
public class ExampleItemReader implements ItemReader<String> {・・・}
----------------------------
@Component("writer")
public class ExampleItemWriter implements ItemWriter<Object> {・・・}
----------------------------

見た通り「@Component」のアノテーションを使用してこのクラスとxml側の設定とを紐づけています。

ちなみにサンプルの場合はデータ整形を行わないので「processor」の設定はしていません。「reader」、「processor」、「writer」に指定したクラスはこの順番で実行されます。

また、指定するクラスはそれぞれ「ItemReader」「ItemProcessor」「ItemWriter」というインターフェースを実装している必要があります。

 

[PR] Javaで挫折しない学習方法を動画で公開中

実際に書いてみよう

下記のクラスを左欄の解説の手順で作成したサンプルプロジェクトに追加してください。

また、「module-context.xml」を下記の様に変更してください。

変更前<batch:chunk reader=”reader” writer=”writer”
変更後<batch:chunk reader=”filereader” processor=”fileprocessor” writer=”filewriter”

実行する前にプロジェクトフォルダの直下にinputフォルダを作成してテキストファイルを置いてください。内容は何でも良いです。また、ファイルは複数有っても良いです。

プロジェクトフォルダの直下にoutputフォルダを作成してout.txtを出力します。

out.txtの内容はinputフォルダ内のファイル内容を連結したものであり、10文字ずつ区切って改行しています。

 

ソースコード

package toplv.midlv.btmlv;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

import org.springframework.batch.item.ItemReader;
import org.springframework.stereotype.Component;

/**
* {@link ItemReader} with hard-coded input data.
*/
@Component("filereader")
public class SampleFileReader implements ItemReader<Object> {

  String fpath = "input";
  private List<String> lines;
  private int index = 0;

  public SampleFileReader() {
    lines = new ArrayList<String>();
    fileRead();
  }

  /**
  * Reads next record from input
  */
  public List<String> read() throws Exception {
      if (index == 0) {
        index++;
        return lines;
      }
      else {
        return null;
      }
  }

  private void fileRead() {
    try {
      File file = new File(fpath);
      //ファイル情報を読み込み
      File[] listfiles = file.listFiles();
      for (int i = 0; i < listfiles.length; i++) {
        //ファイル内容を全文読み込み
        lines.addAll(Files.readAllLines(listfiles[i].toPath(), StandardCharsets.UTF_8));
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

}
package toplv.midlv.btmlv;

import java.util.List;

import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

@Component("fileprocessor")
public class SampleFileProcessor implements ItemProcessor<List<String>, String> {

  public SampleFileProcessor() {
  }

  public String process(List<String> lines) throws Exception {

    String tmp = new String();
    for (String string : lines) {
      tmp += string;
    }

    String rtn = new String();
    int bi = 0;
    int ei = 10;
    while (true) {
      if (tmp.length() > ei) {
        rtn += tmp.substring(bi, ei);
        rtn += System.getProperty("line.separator");
        bi = ei;
        ei += 10;
      }
      else {
        rtn += tmp.substring(bi);
        break;
      }
    }

    return rtn;
  }

}
package toplv.midlv.btmlv;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;

import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;

 

/**
* Dummy {@link ItemWriter} which only logs data it receives.
*/
@Component("filewriter")
public class SampleFileWriter implements ItemWriter<String> {

  String fpath = "output/out.txt";

  /**
  * @see ItemWriter#write(java.util.List)
  */
  public void write(List<? extends String> data) throws Exception {

    try {
      Path path = Paths.get(fpath);
      if (!Files.isDirectory(path.getParent())) {
        Files.createDirectories(path.getParent());
      }
      if (!Files.exists(path)) {
        Files.createFile(path);
      }
      Files.write(Paths.get(fpath), data, StandardCharsets.UTF_8,StandardOpenOption.WRITE);
    } catch (Exception e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    }

  }

}

 

監修してくれたメンター

堀田 悠貴

以前はSEとして某大学病院の電子カルテシステムの保守・開発に携わっていました。
基本的にJavaを使用してクライアント側のプログラム開発を担当していました。

基本業務はJavaでしたが案件次第で色々他のことにも手を出す必要があり、その都度苦労した記憶があります。
現在はその会社を退社して、こちらでのメンター業務のみ行っています。

 

大石ゆかり

内容分かりやすくて良かったです!

田島悠介

ゆかりちゃんも分からないことがあったら質問してね!

大石ゆかり

分かりました。ありがとうございます!

また、オンラインのプログラミングスクールTechAcademyでは、Java講座を開催しています。

JavaやServletの技術を使ってWebアプリケーションの開発を学ぶことができます。

現役エンジニアがパーソナルメンターとして受講生に1人ずつつき、マンツーマンのメンタリングで学習をサポートし、最短4週間で習得することが可能です。

また、現役エンジニアから学べる無料のプログラミング体験会も実施しているので、ぜひ参加してみてください。