CLOVER🍀

That was when it all began.

Quartzで、Jobにデータを渡す(オマケ:JobDetailとTriggerの数の関係について)

これは、なにをしたくて書いたもの?

先日、Quartzを扱ってみました。

JavaのジョブスケジューラーQuartzを試す - CLOVER🍀

CookbookではJobの定義と一緒にJobにデータを渡す方法も扱っているのですが、前回はこちらはスルーしていました。

How-To: Defining a Job (with input data)

今回は、こちらを扱ってみたいと思います。

あと、今回のお題を扱っている時にJobDetailとTriggerの関連付けでちょっとハマったので、こちらもメモしておきます。

QuartzでJobにデータを渡す

QuartzでJobにデータを渡すことを扱っているドキュメントは、このあたりですね。JobDataMapというものを使います。

特に詳しく書いているのは、Tutorialの方ですね。

Lesson 3: More About Jobs and Job Details

こちらの内容を見ていってみます。

Jobをデータを渡すには、JobDetail…Jobのインスタンスを定義する際に、JobBuilder#usingJobDataを使ってJobにデータを渡すように
設定します。

  JobDetail job = newJob(DumbJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "group1"
      .usingJobData("jobSays", "Hello World!")
      .usingJobData("myFloatValue", 3.141f)
      .build();

JobBuilder (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

JobBuilder#usingJobDataに渡したデータは、JobDataMapに格納されることになります。

JobDataMap (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

渡せるデータは、基本的にはStringとプリミティブ(のラッパー)(Boolean、Double、Float、Integer、Long)のようですが、
JobDataMapそのものも渡すことができるので、ドキュメントに書かれているようにシリアライズ可能な任意のデータを保持できるようには
見えます。

The JobDataMap can be used to hold any amount of (serializable) data objects which you wish to have made available to the job instance when it executes.

Jobを永続化した場合はこれらのデータも永続化の際にシリアライズされるらしく、Javaの標準の型以外のものを格納した場合には
互換性に注意すること、という記述があります。

JobからJobDataMapを取得するには、JobDetailから取得する方法とJobExecutionContextから取得する方法の2つがあります。

JobDetailから取得する場合(JobDetail#getJobDataMap)。

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getJobDetail().getJobDataMap();

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }

JobExecutionContextから取得する場合(JobExecutionContext#getMergedJobDataMap)。

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");
      ArrayList state = (ArrayList)dataMap.get("myStateData");
      state.add(new Date());

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }

2つの違いは、以下になります。

  • JobDetailから取得する場合(JobDetail#getJobDataMap) … JobDetail作成時に設定したJobDataMapを取得する
  • JobExecutionContextから取得する場合(JobExecutionContext#getMergedJobDataMap) … JobDetail作成時に設定したJobDataMapと、Triggerに作成時に設定したJobDataMapをマージして取得する(キー名が重複した場合は、Triggerの値で上書き)

このことから、Triggerに対してもJobDataMapを設定できることがわかります。

ちなみに、ここまでずっとJobDataMapを前提にして書いていましたが、JobFactoryの実装次第ではJobに対してJavaBeansとして
setterを介してデータを設定することもできるようです。

  public class DumbJob implements Job {


    String jobSays;
    float myFloatValue;
    ArrayList state;

    public DumbJob() {
    }

    〜省略〜

    public void setJobSays(String jobSays) {
      this.jobSays = jobSays;
    }

    public void setMyFloatValue(float myFloatValue) {
      myFloatValue = myFloatValue;
    }

    public void setState(ArrayList state) {
      state = state;
    }
}

PropertySettingJobFactory (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

JobDataMapかこちらか、どちらを取るかはお好みで…?

では、ドキュメントを見るのはこれくらいにして、実際に使っていってみましょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-126-generic", arch: "amd64", family: "unix"

準備

Maven依存関係など。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
    </dependencies>

SLF4Jが使うロギングライブラリは、Logbackにしました。

設定は、最低限で簡単に用意。

src/main/resources/logback.xml

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

JobDetail定義時に設定したJobDataMapを使う

では、まずはJobDetailの定義時に設定したJobDataMapを使ってみましょう。

Jobは、こんな感じで用意。

src/main/java/org/littlewings/quartz/PrintMessageJob.java

package org.littlewings.quartz;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintMessageJob implements Job {
    Logger logger = LoggerFactory.getLogger(PrintMessageJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();

        JobKey jobKey = jobDetail.getKey();
        String name = jobKey.getName();
        String group = jobKey.getGroup();

        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        String prefix = jobDataMap.getString("prefix");
        String message = jobDataMap.getString("message");
        String suffix = jobDataMap.getString("suffix");

        logger.info(
                "[{} / {}] {}Hello, {}!!!{}",
                name,
                group,
                prefix,
                message,
                suffix
        );
    }
}

JobDataMapは、JobDetailから取得しています。

        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        String prefix = jobDataMap.getString("prefix");
        String message = jobDataMap.getString("message");
        String suffix = jobDataMap.getString("suffix");

元になったJobDetailはというと、JobExecutionContextから取得しているのですが。

        JobDetail jobDetail = context.getJobDetail();

mainメソッドを持ったクラス。

src/main/java/org/littlewings/quartz/SimpleJobDataApp.java

package org.littlewings.quartz;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class SimpleJobDataApp {
    public static void main(String... args) {
        Scheduler scheduler = null;
        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();

            JobDetail printMessageJob1 =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            .withIdentity("printMessageJob1", "job-group1")
                            .usingJobData("prefix", "{")
                            .usingJobData("message", "Quartz Job")
                            .usingJobData("suffix", "}")
                            .build();

            JobDetail printMessageJob2 =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            .withIdentity("printMessageJob2", "job-group1")
                            .usingJobData("prefix", "【")
                            .usingJobData("message", "World")
                            .usingJobData("suffix", "】")
                            .build();

            Trigger trigger1 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .build();

            Trigger trigger2 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger2", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(2)
                                            .repeatForever()
                            )
                            .build();

            scheduler.scheduleJobs(
                    Map.of(
                            printMessageJob1, Set.of(trigger1),
                            printMessageJob2, Set.of(trigger2)
                    ),
                    true
            );

            scheduler.start();

            waitFor(Duration.ofSeconds(20L));
        } catch (SchedulerException e) {
            e.printStackTrace();
        } finally {
            if (scheduler != null) {
                try {
                    scheduler.shutdown(true);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static void waitFor(Duration waitTime) {
        LocalDateTime startTime = LocalDateTime.now();
        while (ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) < waitTime.toSeconds()) {
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                // no-op
            }
        }
    }
}

JobDataMapはJobDetailに対して設定していくので、それなら同じJobであっても別々の設定ができるだろうと思って試してみました。

            JobDetail printMessageJob1 =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            .withIdentity("printMessageJob1", "job-group1")
                            .usingJobData("prefix", "{")
                            .usingJobData("message", "Quartz Job")
                            .usingJobData("suffix", "}")
                            .build();

            JobDetail printMessageJob2 =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            .withIdentity("printMessageJob2", "job-group1")
                            .usingJobData("prefix", "【")
                            .usingJobData("message", "World")
                            .usingJobData("suffix", "】")
                            .build();

Triggerも、それぞれ作成。

            Trigger trigger1 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .build();

            Trigger trigger2 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger2", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(2)
                                            .repeatForever()
                            )
                            .build();

JobDetailとTriggerを、ペアでSchedulerに登録。

            scheduler.scheduleJobs(
                    Map.of(
                            printMessageJob1, Set.of(trigger1),
                            printMessageJob2, Set.of(trigger2)
                    ),
                    true
            );

終了までは、20秒待つことにします。

            waitFor(Duration.ofSeconds(20L));

では、実行。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.SimpleJobDataApp

同じJobに対して、別々のデータを渡して動いていることが確認できます。

2022-10-09 22:37:56.778 [DefaultQuartzScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:37:56.778 [DefaultQuartzScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:37:58.773 [DefaultQuartzScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:37:59.772 [DefaultQuartzScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:38:00.773 [DefaultQuartzScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:02.772 [DefaultQuartzScheduler_Worker-6] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:38:02.772 [DefaultQuartzScheduler_Worker-7] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:04.774 [DefaultQuartzScheduler_Worker-8] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:05.771 [DefaultQuartzScheduler_Worker-9] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:38:06.773 [DefaultQuartzScheduler_Worker-10] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:08.772 [DefaultQuartzScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:38:08.772 [DefaultQuartzScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:10.774 [DefaultQuartzScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:11.771 [DefaultQuartzScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:38:12.774 [DefaultQuartzScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:14.773 [DefaultQuartzScheduler_Worker-6] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 22:38:14.773 [DefaultQuartzScheduler_Worker-7] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:16.774 [DefaultQuartzScheduler_Worker-8] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 22:38:17.771 [DefaultQuartzScheduler_Worker-9] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}

OKですね。

JobDetailとTriggerの定義で設定したJobDataMapをマージして使う

次は、JobDetailとTriggerを定義する際に設定したJobDataMapを、マージして使うことにします。

先ほどのJobと似たようなクラスを用意。

src/main/java/org/littlewings/quartz/PrintMessageMergeDataJob.java

package org.littlewings.quartz;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintMessageMergeDataJob implements Job {
    Logger logger = LoggerFactory.getLogger(PrintMessageMergeDataJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();

        JobKey jobKey = jobDetail.getKey();
        String name = jobKey.getName();
        String group = jobKey.getGroup();

        //JobDataMap jobDataMap = jobDetail.getJobDataMap();
        JobDataMap jobDataMap = context.getMergedJobDataMap();
        String prefix = jobDataMap.getString("prefix");
        String message = jobDataMap.getString("message");
        String suffix = jobDataMap.getString("suffix");

        logger.info(
                "[{} / {}] {}Hello, {}!!!{}",
                name,
                group,
                prefix,
                message,
                suffix
        );
    }
}

クラス名以外の変更点は1点で、JobDataMapの取得元を変更しています。

        //JobDataMap jobDataMap = jobDetail.getJobDataMap();
        JobDataMap jobDataMap = context.getMergedJobDataMap();

次は、mainメソッドを持ったクラス。

src/main/java/org/littlewings/quartz/MergeJobDataApp.java

package org.littlewings.quartz;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class MergeJobDataApp {
    public static void main(String... args) {
        Scheduler scheduler = null;
        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();

            JobDetail printMessageJob1 =
                    JobBuilder
                            .newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob1", "job-group1")
                            .usingJobData("prefix", "{")
                            .usingJobData("message", "Quartz Job")
                            .usingJobData("suffix", "}")
                            .build();

            JobDetail printMessageJob2 =
                    JobBuilder
                            .newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob2", "job-group1")
                            .usingJobData("prefix", "【")
                            .usingJobData("message", "World")
                            .usingJobData("suffix", "】")
                            .build();

            Trigger trigger1 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .usingJobData("message", "Quartz Job from Trigger")
                            .build();

            Trigger trigger2 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger2", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(2)
                                            .repeatForever()
                            )
                            .usingJobData("message", "World from Trigger")
                            .build();

            scheduler.scheduleJobs(
                    Map.of(
                            printMessageJob1, Set.of(trigger1),
                            printMessageJob2, Set.of(trigger2)
                    ),
                    true
            );

            scheduler.start();

            waitFor(Duration.ofSeconds(20L));
        } catch (SchedulerException e) {
            e.printStackTrace();
        } finally {
            if (scheduler != null) {
                try {
                    scheduler.shutdown(true);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static void waitFor(Duration waitTime) {
        LocalDateTime startTime = LocalDateTime.now();
        while (ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) < waitTime.toSeconds()) {
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                // no-op
            }
        }
    }
}

先ほどと、構造はほとんど同じです。

違うのは指定しているJobクラスと

            JobDetail printMessageJob1 =
                    JobBuilder
                            .newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob1", "job-group1")
                            .usingJobData("prefix", "{")
                            .usingJobData("message", "Quartz Job")
                            .usingJobData("suffix", "}")
                            .build();

            JobDetail printMessageJob2 =
                    JobBuilder
                            .newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob2", "job-group1")
                            .usingJobData("prefix", "【")
                            .usingJobData("message", "World")
                            .usingJobData("suffix", "】")
                            .build();

TriggerにJobDataMapを設定していることですね。

            Trigger trigger1 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .usingJobData("message", "Quartz Job from Trigger")
                            .build();

            Trigger trigger2 =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger2", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(2)
                                            .repeatForever()
                            )
                            .usingJobData("message", "World from Trigger")
                            .build();

すべての値を上書きするのではなく、messageをキーにしている部分だけ上書きすることにしました。

最初に、変化を見るためにJobのクラスだけ前に戻してみましょう。TriggerにJobDataMapを設定しているのはそのままにします。

            JobDetail printMessageJob1 =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            //.newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob1", "job-group1")
                            .usingJobData("prefix", "{")
                            .usingJobData("message", "Quartz Job")
                            .usingJobData("suffix", "}")
                            .build();

            JobDetail printMessageJob2 =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            //.newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob2", "job-group1")
                            .usingJobData("prefix", "【")
                            .usingJobData("message", "World")
                            .usingJobData("suffix", "】")
                            .build();

実行。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.MergeJobDataApp

先ほどと同じ結果になります。

2022-10-09 23:14:56.895 [DefaultQuartzScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:14:56.895 [DefaultQuartzScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:14:58.888 [DefaultQuartzScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:14:59.887 [DefaultQuartzScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:15:00.889 [DefaultQuartzScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:02.886 [DefaultQuartzScheduler_Worker-6] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:15:02.886 [DefaultQuartzScheduler_Worker-7] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:04.889 [DefaultQuartzScheduler_Worker-8] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:05.887 [DefaultQuartzScheduler_Worker-9] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:15:06.889 [DefaultQuartzScheduler_Worker-10] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:08.886 [DefaultQuartzScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:15:08.887 [DefaultQuartzScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:10.888 [DefaultQuartzScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:11.886 [DefaultQuartzScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:15:12.889 [DefaultQuartzScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:14.888 [DefaultQuartzScheduler_Worker-6] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}
2022-10-09 23:15:14.888 [DefaultQuartzScheduler_Worker-7] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:16.889 [DefaultQuartzScheduler_Worker-8] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob2 / job-group1] 【Hello, World!!!】
2022-10-09 23:15:17.886 [DefaultQuartzScheduler_Worker-9] INFO  o.littlewings.quartz.PrintMessageJob - [printMessageJob1 / job-group1] {Hello, Quartz Job!!!}

では、今回用意したJobに戻してみます。

            JobDetail printMessageJob1 =
                    JobBuilder
                            //.newJob(PrintMessageJob.class)
                            .newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob1", "job-group1")
                            .usingJobData("prefix", "{")
                            .usingJobData("message", "Quartz Job")
                            .usingJobData("suffix", "}")
                            .build();

            JobDetail printMessageJob2 =
                    JobBuilder
                            //.newJob(PrintMessageJob.class)
                            .newJob(PrintMessageMergeDataJob.class)
                            .withIdentity("printMessageJob2", "job-group1")
                            .usingJobData("prefix", "【")
                            .usingJobData("message", "World")
                            .usingJobData("suffix", "】")
                            .build();

実行。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.MergeJobDataApp

今度は、messageの値がTriggerで渡した値で上書きされていることが確認できます。

2022-10-09 23:16:17.105 [DefaultQuartzScheduler_Worker-1] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:17.105 [DefaultQuartzScheduler_Worker-2] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:19.100 [DefaultQuartzScheduler_Worker-3] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:20.097 [DefaultQuartzScheduler_Worker-4] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:21.100 [DefaultQuartzScheduler_Worker-5] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:23.097 [DefaultQuartzScheduler_Worker-6] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:23.097 [DefaultQuartzScheduler_Worker-7] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:25.100 [DefaultQuartzScheduler_Worker-8] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:26.097 [DefaultQuartzScheduler_Worker-9] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:27.100 [DefaultQuartzScheduler_Worker-10] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:29.097 [DefaultQuartzScheduler_Worker-2] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:29.097 [DefaultQuartzScheduler_Worker-1] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:31.100 [DefaultQuartzScheduler_Worker-3] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:32.097 [DefaultQuartzScheduler_Worker-4] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:33.100 [DefaultQuartzScheduler_Worker-5] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:35.098 [DefaultQuartzScheduler_Worker-6] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}
2022-10-09 23:16:35.098 [DefaultQuartzScheduler_Worker-7] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:37.099 [DefaultQuartzScheduler_Worker-8] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob2 / job-group1] 【Hello, World from Trigger!!!】
2022-10-09 23:16:38.098 [DefaultQuartzScheduler_Worker-9] INFO  o.l.quartz.PrintMessageMergeDataJob - [printMessageJob1 / job-group1] {Hello, Quartz Job from Trigger!!!}

これで、今回の目的は確認できた感じですね。

オマケ

JobDetailをTriggerに紐付けようとして、ちょっとハマりまして。

今回、JobDetailとTriggerを1対1にする形にしました。

            scheduler.scheduleJobs(
                    Map.of(
                            printMessageJob1, Set.of(trigger1),
                            printMessageJob2, Set.of(trigger2)
                    ),
                    true
            );

ひとつのJobDetailに対して、複数のTriggerを紐付けることはできます。

scheduler.scheduleJob(printMessageJob1, Set.of(trigger1, trigger2), true);

1つのTriggerを、複数のJobDetailに紐付けることはできないようです。

            scheduler.scheduleJob(printMessageJob1, trigger1);
            scheduler.scheduleJob(printMessageJob2, trigger1);

実行すると、2つ目のJobDetailに紐付けようとしたところで例外になります。

org.quartz.SchedulerException: Trigger does not reference given job!
    at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:838)
    at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:249)
    at org.littlewings.quartz.SimpleJobDataApp.main(SimpleJobDataApp.java:79)

一括で登録しても同じですね。

            scheduler.scheduleJobs(
                    Map.of(
                            printMessageJob1, Set.of(trigger1),
                            printMessageJob2, Set.of(trigger1)
                    ),
                    true
            );

ドキュメント上もあんまり明示的には書いていないように見えますが、こちらの1文を見るとひとつのJobに対して定義するように見えますね。

Trigger - a component that defines the schedule upon which a given Job will be executed.

Lesson 2: The Quartz API, Jobs And Triggers

ちょっとした副次効果でした。

まとめ

Quartzで、Jobに対してデータを渡す方法を調べてみました。

割とあっさりした話だと思っていたのですが、Triggerのデータとマージできたりするのは気づいていなかったですね。

あと、永続化に関する記述も見れて良かったです。そのうち、Jobの永続化についても確認してみましょう。