【Spring Boot】@Scheduledでバッチ処理を実装する方法【排他制御まで解説】

Spring BootでWebアプリケーションを開発していると、API処理だけでなく、定期的に実行したい処理が必要になることがあります。

例えば、以下のような処理です。

・5分おきに未処理データを確認する
・15分おきに外部APIと連携する
・毎日深夜に集計処理を実行する
・古いデータを定期的に削除する

このような定期実行処理は、一般的にバッチ処理と呼ばれます。Spring Bootでは、@Scheduled を使うことで、比較的簡単にバッチ処理を実装できます。この記事では、初心者の方でも実装できるように、@Scheduled の基本的な使い方から、ShedLockを使った排他制御まで解説します。

@Scheduledとは

@Scheduled は、Spring Frameworkが提供している定期実行用のアノテーションです。メソッドに @Scheduled を付けることで、指定した間隔や時刻で処理を自動実行できます。

たとえば、15分おきに処理を実行したい場合は、以下のように書きます。

@Scheduled(cron = "0 */15 * * * *", zone = "Asia/Tokyo")
public void execute() {
    // 15分おきに実行したい処理
}

cron式は 秒 分 時 日 月 曜日 の順で指定します。 0 */15 * * * * は、毎時0分、15分、30分、45分に実行するという意味です。

@Scheduledを有効化する

@Scheduled を使うには、設定クラスに @EnableScheduling を付けます。

@Configuration
@EnableScheduling
public class SchedulerConfig {
}

これで、Springが @Scheduled の付いたメソッドを検出して、定期実行してくれるようになります。

シンプルなバッチ処理の実装例

まずは、15分おきに実行されるシンプルなバッチを作成します。

@Component
public class SampleBatchScheduler {
    private final SampleBatchService sampleBatchService;

    @Scheduled(cron = "0 */15 * * * *", zone = "Asia/Tokyo")
    public void execute() {
        sampleBatchService.execute();
    }
}

実際の処理は SampleBatchService に分けます。

@Service
public class SampleBatchService {
    public void execute() {
        // バッチ本体の処理を書く
    }
}

複数インスタンスでは二重実行に注意

本番環境では、Spring Bootアプリを複数台で動かすことがあります。この状態で @Scheduled を使うと、各サーバーで同じバッチが実行される可能性があります。同じ処理が重複して実行されないように、ShedLockを使って排他制御を行います。

ShedLockとは

ShedLockは、Springのスケジューラに分散ロックを追加するライブラリです。複数のインスタンスがあっても、同じバッチを1台だけが実行するようにできます。ロックを取得できなかった場合、処理を待たずにスキップするため、二重実行を防げます。

ShedLockを使うには、依存関係を追加し、ロック用のテーブルを作成します。MySQLの場合は、下記のようなSQLを実行しておきます。

CREATE TABLE shedlock (
    name VARCHAR(64) NOT NULL,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    locked_by VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

設定クラスで @EnableSchedulerLock を有効にし、LockProvider を設定します。

@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "30m")
public class SchedulerConfig {
}

@Configuration
public class ShedLockConfig {
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
            JdbcTemplateLockProvider.Configuration.builder()
                .withJdbcTemplate(new JdbcTemplate(dataSource))
                .usingDbTime()
                .build()
        );
    }
}

ShedLockを使った実装例

ShedLockを組み合せて15分バッチを実装すると、以下のようになります。

@Component
public class SampleBatchScheduler {
    private final SampleBatchService sampleBatchService;

    @Scheduled(cron = "0 */15 * * * *", zone = "Asia/Tokyo")
    @SchedulerLock(
        name = "SampleBatchScheduler.execute",
        lockAtMostFor = "30m",
        lockAtLeastFor = "14m"
    )
    public void execute() {
        sampleBatchService.execute();
    }
}

name はロック名です。同じ名前のタスクは同時に実行されません。lockAtMostFor はロックの最大保持時間で、アプリが強制終了した場合でも期限切れでロックが解放されます。lockAtLeastFor は最位保持時間で、短い間隔で連続実行されるのを防ぎたい場合に指定しま。

複数のバッチがある場合の注意点

同じアプリ内に5分バッチ、15分バッチを含める場合は、実行タイミングをずらしたり、ロック名を分けるなどの工夫が必要です。また、スケジューラのスレッド数やDB接続数も調整し、API処理とバッチ処理がリソースを奪い合わないようにしましょう。

是非フォローしてください

最新の情報をお伝えします

類似投稿