モダンJavaバッチ開発:Spring Batch Taskletでデータ処理以外のバッチ(ファイル操作、DB初期化、外部API連携など)を実装しよう!

「Spring Batchって、大量データ処理のためだけもの?」

もしあなたがそう思っているなら、それは大きな誤解です。
Spring Batchは、データ処理だけでなく、ファイル操作、DB初期化、外部API連携といった「単発のタスク」もシンプルに実装できる強力なフレームワークです。

この記事では、Spring Batchの隠れた主役とも言える Tasklet に焦点を当て、その基本的な概念から、実用的な3つのシナリオでの実装方法までを、初心者の方にも分かりやすく徹底解説します。

この記事を読み終える頃には、Spring BatchのTaskletを使って、これまで「ちょっと面倒だな…」と感じていた単発タスクを、スマートに自動化できるようになっているはずです。

一緒にTaskletの奥深い世界を探検し、あなたのバッチ処理スキルを次のレベルへと引き上げましょう!


この記事の対象読者

  • Spring Batchの基本的な概念は理解しているが、Taskletの具体的な使い方やChunk処理との使い分けに迷っている方。
  • データ処理以外のバッチ処理(ファイル操作、DB初期化、外部API連携など)をSpring Batchで実装したいと考えている方。
  • 実践的なコード例を通して、Spring BatchのTasklet実装を学びたい方。
  • 業務システムでSpring Batchの導入を検討しており、Taskletの適用範囲を知りたい方。

この記事で学べること

  • Taskletの基本的な概念と、Chunk処理との違い
  • ファイル操作、DB操作、外部API連携といった、実用的なTaskletの実装方法
  • Taskletを組み込んだSpring Batchジョブの定義と実行方法

目次


1. Taskletとは? Chunk処理との違い

1-1. Taskletの役割

Taskletは、Spring Batchにおいて単一のタスク(a single task) を実行するためのシンプルなインターフェースです。
executeという一つのメソッドだけを持ち、その中に実行したい処理を自由に記述できます。

public interface Tasklet {
    RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception;
}

executeインターフェースの補足:

  • StepContribution:
    • 現在のステップの実行状況(コミット数、読み込み数など)を更新するために使用します。
  • ChunkContext:
    • 現在のチャンク(Taskletの場合はステップ全体)の実行コンテキスト情報(ステップ名、ジョブ名など)を提供します。
  • RepeatStatus.FINISHED:
    • タスクが完了し、これ以上繰り返す必要がないことを示します。
  • RepeatStatus.CONTINUABLE:
    • タスクがまだ完了しておらず、次の繰り返しで再度実行される必要があることを示します。例えば、大量のファイルを順次処理するTaskletなどで使用することがあります。

処理が完了したら RepeatStatus.FINISHED を返すだけなので、直感的になっています。


1-2. Chunk処理との比較

一方、Spring Batchの代表的な処理モデルであるChunk指向処理は、大量のデータを扱うために設計されています。

特徴TaskletChunk指向処理
目的単一のタスクを実行する大量のデータをチャンク(塊)単位で処理する
処理単位ステップ全体で1つのタスクRead-Process-Writeの繰り返し
主な構成要素TaskletインターフェースItemReader, ItemProcessor, ItemWriter
トランザクションexecuteメソッド全体で1回チャンク単位(コミットインターバルごと)
得意なことファイル操作、DB初期化、API連携などCSVファイルからDBへのデータ移行など

簡単に言うと、以下のように例えられます。

  • Chunk処理:
    • 「ベルトコンベアに乗って流れてくる大量の製品を、一つずつ加工していく工場ライン」
  • Tasklet:
    • 「工場ラインの準備(機械のセットアップ)や、加工後の製品の梱包・出荷といった、単発で完結する専門作業」

つまり、「繰り返し行うデータ処理」ならChunk処理「一度だけ実行したい特定のタスク」ならTasklet、と覚えておくと良いでしょう。


1-3. Taskletが活躍するシナリオ

Taskletは、一度だけ実行する特定のタスクを実装するのに向いています。以下に、Taskletが適用できるシナリオをいくつか挙げます。

  • ファイルの移動や削除:
    • バッチ処理の開始前にインプットファイルを特定の場所に移動したり、処理後に不要なファイルを削除したりする。
  • データベースの初期化:
    • 処理に使う一時テーブルをTRUNCATEしたり、テストデータを投入したりする。
  • 外部APIの呼び出し:
    • 処理結果を外部システムに通知したり、外部から設定情報を取得したりする。
  • シェルスクリプトの実行:
    • 既存のスクリプトをバッチフローに組み込む。
  • リソースの検証:
    • 処理に必要なファイルやDB接続が存在するかどうかを確認する。

今回は、この中から代表的な3つのシナリオ(ファイル移動、DB初期化、外部API呼出)を実装していきます。


Spring BatchのChunk処理の詳細については、以下の記事で詳細に解説していますので、是非ご覧ください。


2. ハンズオン:3つのTaskletを作ってみよう

ここからは、実際にコードを書きながら3つの異なるTaskletを作成していきます。


2-1. 準備:プロジェクトの確認

今回のプロジェクトでは、spring-boot-starter-batchspring-boot-starter-web、そしてH2データベースを使用します。pom.xmlの依存関係は以下のようになっています。

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

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

そして、これから作成するクラスと設定の全体像は以下の通りです。

  • Taskletクラス:
    • FileMovingTasklet.java: ファイルを移動する
    • TruncateTableTasklet.java: DBテーブルを空にする
    • ExternalApiCallTasklet.java: 外部APIを呼び出す
  • Batch設定:
    • BatchConfig.java: 上記3つのTaskletをStepとして登録し、それらを順次実行するJobを定義する
  • コントローラー:
    • JobLaunchController.java: 作成したJobをHTTPリクエストで起動するためのエンドポイントを定義する

Spring Batchのサンプルコードについては、以下の記事で詳細に解説していますので、是非ご覧ください。


2-2. シナリオ1:ファイルの移動 (FileMovingTasklet)

最初に、指定された入力ファイルを別のディレクトリに移動するTaskletを作成します。
これは、処理済みファイルをアーカイブするような後処理でよく使われます。

src/main/java/com/example/springbatchh2crud/tasklet/FileMovingTasklet.java

package com.example.springbatchh2crud.tasklet;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

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

@Slf4j
public class FileMovingTasklet implements Tasklet {

    private final String inputFile;
    private final String outputDirectory;

    public FileMovingTasklet(String inputFile, String outputDirectory) {
        this.inputFile = inputFile;
        this.outputDirectory = outputDirectory;
    }

    @Override
    public RepeatStatus execute(
        StepContribution contribution,
        ChunkContext chunkContext
    ) throws Exception {
        // 実行ディレクトリを基準にパスを解決
        Path currentDir = Paths.get(System.getProperty("user.dir"));
        Path source = currentDir.resolve("src/main/resources/" + inputFile);
        Path targetDir = currentDir.resolve("src/main/resources/" + outputDirectory);

        if (!Files.exists(targetDir)) {
            Files.createDirectories(targetDir);
            log.info("Created directory: {}", targetDir);
        }

        Path target = targetDir.resolve(source.getFileName());

        Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
        log.info("Moved file from {} to {}", source, target);

        return RepeatStatus.FINISHED;
    }
}

コード解説

  • Taskletインターフェースを実装し、executeメソッドをオーバーライドします。
  • コンストラクタで、移動元のファイルパス (inputFile) と移動先のディレクトリパス (outputDirectory) を受け取ります。
  • executeメソッド内では、System.getProperty("user.dir") を使用してアプリケーションの実行ディレクトリを基準にファイルのパスを解決しています。これにより、ClassPathResourceの環境依存性を避け、より堅牢なファイル操作を実現しています。
  • Files.createDirectories(targetDir)で、移動先ディレクトリがなければ作成します。
  • Files.move(...)で、実際にファイルを移動します。StandardCopyOption.REPLACE_EXISTINGを指定することで、もし同名ファイルが存在しても上書きします。
  • 処理が正常に完了したら、RepeatStatus.FINISHEDを返します。これにより、このStepは完了したとSpring Batchに通知されます。

2-3. シナリオ2:DBテーブルの初期化 (TruncateTableTasklet)

次に、データベースの特定のテーブルを空にする(TRUNCATEする)Taskletを作成します。
バッチ処理の実行前に、出力先のテーブルをクリーンアップするような前処理で役立ちます。

src/main/java/com/example/springbatchh2crud/tasklet/TruncateTableTasklet.java

package com.example.springbatchh2crud.tasklet;

import org.springframework.batch.core.StepContribution;
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.jdbc.core.JdbcTemplate;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TruncateTableTasklet implements Tasklet {

    private final JdbcTemplate jdbcTemplate;
    private final String tableName;

    public TruncateTableTasklet(JdbcTemplate jdbcTemplate, String tableName) {
        this.jdbcTemplate = jdbcTemplate;
        this.tableName = tableName;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        log.info("Deleting all records from table: {}", tableName);
        jdbcTemplate.update("DELETE FROM " + tableName);
        log.info("Successfully deleted all records from table: {}", tableName);
        return RepeatStatus.FINISHED;
    }
}

コード解説

  • こちらもTaskletインターフェースを実装しています。
  • コンストラクタで、SQLを実行するためのJdbcTemplateと、対象のテーブル名tableNameを受け取ります。
  • executeメソッド内で、jdbcTemplate.update("DELETE FROM " + tableName)を呼び出すことで、指定されたテーブルの全データを削除します。TRUNCATE TABLEはデータベースによってはトランザクション管理下に置けない場合があるため、ここではDELETE FROMを使用しています。
  • ログを出力し、処理が完了したことを示してからRepeatStatus.FINISHEDを返します。

2-4. シナリオ3:外部APIの呼び出し (ExternalApiCallTasklet)

最後に、外部のAPIを呼び出して結果をログに出力するTaskletを作成します。
バッチ処理の完了をSlackに通知したり、外部のデータソースから情報を取得したりする際に利用できます。

src/main/java/com/example/springbatchh2crud/tasklet/ExternalApiCallTasklet.java

package com.example.springbatchh2crud.tasklet;

import org.springframework.batch.core.StepContribution;
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.web.client.RestTemplate;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ExternalApiCallTasklet implements Tasklet {

    private final RestTemplate restTemplate;
    private final String apiUrl;

    public ExternalApiCallTasklet(RestTemplate restTemplate, String apiUrl) {
        this.restTemplate = restTemplate;
        this.apiUrl = apiUrl;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        log.info("Calling external API: {}", apiUrl);
        String response = restTemplate.getForObject(apiUrl, String.class);
        log.info("API Response: {}", response);
        return RepeatStatus.FINISHED;
    }
}

コード解説

  • コンストラクタで、API呼び出しに使うRestTemplateと、呼び出し先のapiUrlを受け取ります。
  • executeメソッド内で、restTemplate.getForObject(apiUrl, String.class)を実行し、APIからレスポンスを文字列として受け取ります。
  • 受け取ったレスポンスをログに出力し、RepeatStatus.FINISHEDを返します。今回はダミーAPIとしてjsonplaceholderを使用しています。

3. ジョブの組み立てと実行

3つのTaskletが完成したので、これらをSpring BatchのJobとして組み立てて、実行できるようにしましょう。


3-1. StepとJobの定義 (BatchConfig.java)

BatchConfig.javaに、作成したTaskletStepとして登録し、それらを連結して一つのJobを定義します。

    // --- Tasklet-based Job ---

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public FileMovingTasklet fileMovingTasklet() {
        return new FileMovingTasklet("input/input.csv", "processed");
    }

    @Bean
    public TruncateTableTasklet truncateTableTasklet() {
        return new TruncateTableTasklet(jdbcTemplate, "summaries");
    }

    @Bean
    public ExternalApiCallTasklet externalApiCallTasklet() {
        return new ExternalApiCallTasklet(
            restTemplate(),
            "https://jsonplaceholder.typicode.com/posts/1"
        );
    }

    @Bean
    public Step fileMovingStep() {
        return new StepBuilder("fileMovingStep", jobRepository)
            .tasklet(fileMovingTasklet(), transactionManager)
            .build();
    }

    @Bean
    public Step truncateTableStep() {
        return new StepBuilder("truncateTableStep", jobRepository)
            .tasklet(truncateTableTasklet(), transactionManager)
            .build();
    }

    @Bean
    public Step externalApiCallStep() {
        return new StepBuilder("externalApiCallStep", jobRepository)
            .tasklet(externalApiCallTasklet(), transactionManager)
            .build();
    }

    @Bean
    public Job taskletJob(
        Step fileMovingStep,
        Step truncateTableStep,
        Step externalApiCallStep
    ) {
        return new JobBuilder("taskletJob", jobRepository)
            .start(fileMovingStep)
            .next(truncateTableStep)
            .next(externalApiCallStep)
            .build();
    }

コード解説

  • TaskletのBean化:
    • 作成した3つのTaskletRestTemplate@BeanメソッドでSpringのコンテナに登録します。これにより、他の場所でDI(依存性注入)できるようになります。
  • Stepの定義:
    • StepBuilderを使ってStepを定義します。
    • .tasklet()メソッドに、実行したいTaskletのBeanを渡すだけで、TaskletベースのStepが完成します。
  • Jobの定義:
    • JobBuilderを使ってJobを定義します。
    • .start()で最初のStepを指定し、.next()で次のStepをチェーンのようにつなげていくことで、実行順序を制御できます。
    • 今回は「ファイル移動 → テーブル初期化 → API呼び出し」の順で実行されます。

3-2. ジョブ実行のトリガー作成 (JobLaunchController.java)

次に、作成したtaskletJobをHTTPリクエストで起動できるように、JobLaunchControllerを修正します。

@RestController
public class JobLaunchController {

    private final JobLauncher jobLauncher;
    private final Job userOperationJob;
    private final Job taskletJob;

    /**
     * 依存性注入のためのコンストラクタ。
     * @param jobLauncher Spring BatchのJobLauncher。
     * @param userOperationJob 起動する特定のジョブ。"userOperationJob"というBean名で識別されます。
     * @param taskletJob 起動する特定のジョブ。"taskletJob"というBean名で識別されます。
     */
    @Autowired
    public JobLaunchController(
        JobLauncher jobLauncher,
        @Qualifier("userOperationJob") Job userOperationJob,
        @Qualifier("taskletJob") Job taskletJob
    ) {
        this.jobLauncher = jobLauncher;
        this.userOperationJob = userOperationJob;
        this.taskletJob = taskletJob;
    }

    // ... (launchJob method)

    /**
     * /launch-tasklet-jobにGETリクエストが行われたときに'taskletJob'を起動します。
     * @return ジョブの起動試行の結果を示すResponseEntity。
     */
    @GetMapping("/launch-tasklet-job")
    public ResponseEntity<String> launchTaskletJob() {
        try {
            JobParameters jobParameters = new JobParametersBuilder()
                .addLong("time", System.currentTimeMillis())
                .toJobParameters();

            jobLauncher.run(taskletJob, jobParameters);

            return ResponseEntity.ok(
                "ジョブ '" + taskletJob.getName() + "' は正常に起動されました。"
            );
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body(
                "ジョブの起動に失敗しました: " + e.getMessage()
            );
        }
    }
}

コード解説

  1. taskletJobという名前のJob Beanをインジェクションするために、フィールドを追加します。
  2. コンストラクタの引数に@Qualifier("taskletJob") Job taskletJobを追加し、BatchConfigで定義したtaskletJobをインジェクションします。
  3. @GetMapping("/launch-tasklet-job")で、新しいAPIエンドポイントを作成します。
  4. jobLauncher.run()メソッドの引数に、インジェクションしたtaskletJobを渡して実行します。

3-3. 実行と結果の確認

これで全ての準備が整いました。アプリケーションを起動して、ジョブを実行してみましょう。

1. アプリケーションの起動
プロジェクトのルートディレクトリで、以下のコマンドを実行します。

./mvnw spring-boot:run

2. ジョブの実行
ターミナルからcurlコマンドを実行して、先ほど作成したエンドポイントにリクエストを送ります。

curl http://localhost:8080/launch-tasklet-job

「ジョブ ‘taskletJob’ は正常に起動されました。」というメッセージが返ってくれば成功です。

3. 結果の検証
ジョブが意図通りに動作したか、3つのステップの結果をそれぞれ確認します。

  • ファイル移動の確認:
    src/main/resources/input/input.csv がなくなり、代わりに src/main/resources/processed/input.csv が作成されていることを確認します。
  • DBテーブルの確認:
    ブラウザでH2コンソール(http://localhost:8080/h2-console)を開き、JDBC URLにjdbc:h2:mem:testdb、ユーザー名にsaを指定して接続します。
    SELECT * FROM SUMMARY; を実行し、結果が0件であることを確認します。
  • API呼び出しの確認:
    アプリケーションを起動したコンソールのログに、以下のようなAPIレスポンスが出力されていることを確認します。
2025-11-16T14:14:56.885+09:00  INFO 5740 --- [spring-batch-h2-crud] [nio-8080-exec-1] c.e.s.tasklet.ExternalApiCallTasklet     : API Response: {
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

4. まとめ

今回は、Spring BatchのTaskletを使って、データ処理以外の単発タスクを実行する方法を学びました。

  • Taskletはシンプル: executeメソッドに処理を書くだけで、簡単にタスクを実装できます。
  • Taskletは汎用的: ファイル操作、DB操作、API連携など、アイデア次第で様々な処理に応用できます。
  • Taskletは組み合わせ可能: Jobの定義で複数のStepを.next()でつなぐことで、複雑なワークフローも構築できます。

Chunk処理とTaskletを適切に使い分けることで、Spring Batchの可能性はさらに広がります。例えば、Taskletで前処理を行った後、Chunk処理で本番のデータ移行を行い、最後にTaskletで結果を通知する、といった連携も可能です。

この記事が、あなたのSpring Batchライフの一助となれば幸いです。


免責事項

本記事の内容は、執筆時点での情報に基づいています。
技術情報は常に更新されるため、最新の公式ドキュメントや情報源を参照することを強く推奨します。
本記事を利用したことによるいかなる損害に対しても、著者は一切の責任を負いません。コードの利用は自己責任でお願いします。


SNSでもご購読できます。

コメントを残す

*