Spring BootにおけるQuartz統合ガイド

本稿では、Spring BootアプリケーションにおいてQuartzを使用してスケジューリングタスクを実装する手法を詳述する。QuartzはJava環境で広く利用されているオープンソースの_jobスケジューリングライブラリであり、柔軟なcron式に基づく実行タイミングの制御が可能である。

依存関係の定義

Mavenプロジェクトにおいて、spring-boot-starter-quartz依存関係をpom.xmlに追加することでQuartz機能を利用できる。以下に設定例を示す。

<?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 http://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.3.2.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>org.sample</groupId>
    <artifactId>quartz-scheduler-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

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

タスク情報のデータ転送オブジェクト

スケジューリング対象タスクの各種属性を保持するDTOクラスを定義する。このクラスは_job_key、trigger_key、cron式などの情報を一元管理する役割を果たす。

package org.sample.dto;

import lombok.Data;
import org.quartz.Job;

@Data
public class ScheduledTaskInfo {
    /**
     * タスク識別名
     */
    private String taskName;

    /**
     * タスクグループ識別子
     */
    private String taskGroup;

    /**
     * トリガー識別名
     */
    private String triggerName;

    /**
     * トリガーグループ識別子
     */
    private String triggerGroup;

    /**
     * Cron実行式
     */
    private String cronExpression;

    /**
     * 実装クラス完全修飾名
     */
    private String className;

    /**
     * 現在のステータス
     */
    private String status;

    /**
     * 次回実行予定時刻
     */
    private String nextExecutionTime;

    /**
     * 前回実行完了時刻
     */
    private String lastExecutionTime;

    /**
     * 拡張設定データ
     */
    private String extendedData;

    /**
     * 実行ロジック実装クラス
     */
    private Class<? extends Job> executionClass;

    // ゲッター・セッターメソッドはLombokの@Dataアノテーションにより自動生成
}

スケジューラ設定クラス

Schedulerインスタンスの初期化および_job登録処理を実装するConfigクラスを定義する。@PostConstructアノテーションを付与することで、bean生成時に初期タスクが自動登録される。

package org.sample.config;

import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.sample.dto.ScheduledTaskInfo;
import org.sample.job.SampleJobTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
@RequiredArgsConstructor
public class TaskSchedulerConfig {

    private final Scheduler scheduler;

    @Bean
    public void initializeScheduledTasks() {
        registerTaskDefinition(createTaskInfo());
    }

    private ScheduledTaskInfo createTaskInfo() {
        ScheduledTaskInfo info = new ScheduledTaskInfo();
        info.setTaskName("sampleTask");
        info.setTaskGroup("sampleTaskGroup");
        info.setTriggerGroup("sampleTriggerGroup");
        info.setExecutionClass(SampleJobTask.class);
        info.setTriggerName("sampleTaskTrigger");
        info.setCronExpression("0/5 * * * * ?");
        return info;
    }

    private void registerTaskDefinition(ScheduledTaskInfo taskInfo) {
        JobKey jobKey = JobKey.jobKey(taskInfo.getTaskName(), taskInfo.getTaskGroup());

        JobDetail jobDetail = JobBuilder.newJob(taskInfo.getExecutionClass())
                .withIdentity(jobKey)
                .storeDurably()
                .build();

        TriggerKey triggerKey = TriggerKey.triggerKey(taskInfo.getTriggerName(), taskInfo.getTriggerGroup());

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCronExpression())
                        .withMisfireHandlingInstructionDoNothing())
                .build();

        try {
            if (scheduler.checkExists(jobKey)) {
                scheduler.deleteJob(jobKey);
            }
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new RuntimeException("タスク登録中にエラーが発生しました", e);
        }
    }
}

タスク実装クラス

QuartzJobBeanを拡張し、実際の実行ロジックを実装する。このクラスはSpring管理のbeanであるため、依存性注入を活用したビジネスロジックの呼び出しが可能である。

package org.sample.job;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.sample.service.TaskExecutionService;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RequiredArgsConstructor
public class SampleJobTask extends QuartzJobBean {

    private final TaskExecutionService executionService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        log.info("{} - Quartzタスクを実行中...", timestamp);

        try {
            executionService.performScheduledOperation();
        } catch (Exception e) {
            log.error("タスク実行エラー", e);
            throw new JobExecutionException(e);
        }
    }
}

ビジネスサービスクラス

タスクから呼び出されるサービス層を実装する。Springの@Serviceアノテーションにより管理beanとして登録される。

package org.sample.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@Service
public class TaskExecutionService {

    public void performScheduledOperation() {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        log.info("{} - サービス層ビジネスロジックを実行中...", timestamp);
    }
}

実行タイミングの動的更新

スケジューリングされたタスクの実行間隔は、アプリケーション稼働中に動的に変更可能である。以下にトリガーを再設定するメソッドの実装を示す。

package org.sample.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class TaskScheduleUpdater {

    private final Scheduler scheduler;

    /**
     * 指定されたタスクのcron式を更新する
     * @param targetTaskName 更新対象タスク名
     * @param targetGroup タスクグループ名
     * @param newCronExpression 新的cron式
     */
    public void updateTaskSchedule(String targetTaskName, String targetGroup, String newCronExpression) {
        String triggerName = targetTaskName + "Trigger";
        String triggerGroup = targetGroup + "TriggerGroup";

        TriggerKey targetTriggerKey = TriggerKey.triggerKey(triggerName, triggerGroup);

        try {
            Trigger existingTrigger = scheduler.getTrigger(targetTriggerKey);
            if (existingTrigger == null) {
                log.warn("指定されたトリガーが見つかりません: {}", targetTriggerKey);
                return;
            }

            Trigger newTrigger = TriggerBuilder.newTrigger()
                    .withIdentity(targetTriggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(newCronExpression)
                            .withMisfireHandlingInstructionDoNothing())
                    .build();

            scheduler.rescheduleJob(targetTriggerKey, newTrigger);
            log.info("タスク '{}' の実行スケジュールを更新しました。新的cron式: {}", targetTaskName, newCronExpression);

        } catch (SchedulerException e) {
            log.error("スケジュール更新中にエラーが発生しました", e);
            throw new RuntimeException("スケジュール更新失敗", e);
        }
    }

    /**
     * タスクの一時停止
     */
    public void pauseTask(String taskName, String taskGroup) {
        try {
            JobKey jobKey = JobKey.jobKey(taskName, taskGroup);
            scheduler.pauseJob(jobKey);
            log.info("タスクを一時停止しました: {}", jobKey);
        } catch (SchedulerException e) {
            log.error("タスク一時停止エラー", e);
        }
    }

    /**
     * タスクの再開
     */
    public void resumeTask(String taskName, String taskGroup) {
        try {
            JobKey jobKey = JobKey.jobKey(taskName, taskGroup);
            scheduler.resumeJob(jobKey);
            log.info("タスクを再開しました: {}", jobKey);
        } catch (SchedulerException e) {
            log.error("タスク再開エラー", e);
        }
    }
}

アプリケーション設定ファイル

application.ymlにおいてQuartzの動作特性を設定する。JDBC永続化を使用する場合、データベーススキーマの自動初期化を有効化する。

server:
  port: 8080

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/scheduler_db
    username: scheduler_user
    password: secure_password
    driver-class-name: org.postgresql.Driver

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Tokyo
    serialization:
      write-dates-as-timestamps: false

  quartz:
    auto-startup: true
    job-store-type: jdbc
    jdbc:
      initialize-schema: always
    properties:
      org:
        quartz:
          threadPool:
            threadCount: 5
            threadPriority: 5
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
            tablePrefix: qrtz_
            useProperties: false

まとめ

本稿ではSpring Boot環境におけるQuartzスケジューラの基本的な統合手法を解説した。spring-boot-starter-quartzを使用することで、複雑な設定なしに_quartz機能をアプリケーションに統合できる。スケジューラの動的更新機能を組み合わせることで、 runtimeにおけるタスク管理柔軟なシステムの構築が可能となる。

タグ: spring-boot Quartz scheduler Java cron

7月29日 16:09 投稿