CLOVER🍀

That was when it all began.

JavaでULIDを䜿いたいSulky ULIDを䜿う

これは、なにをしたくお曞いたもの

JavaでULIDを1床䜿っおおきたいな、ず思いたしお。

ULID

ULIDは、Universally Unique Lexicographically Sortable Identifierの略です。蟞曞的に゜ヌト可胜でナニヌクなID、ですね。

ULIDに぀いおは、こちらのGitHub Organizationにたずたっおいるようです。

ULID · GitHub

仕様はこちらのリポゞトリにあり、

GitHub - ulid/spec: The canonical spec for ulid

README.mdに曞かれおいたす。

https://github.com/ulid/spec/blob/master/README.md

README.mdの内容ですが、ULIDはUUIDの以䞋を課題ずしお提案されたもののようです。

  • 128ビットのランダム性を゚ンコヌドする最も文字効率の良い方法ではない
  • UUID v1およびv2は䞀意性のためにMACアドレスにアクセスする必芁があり、倚くの環境では実甚性がない
  • UUID v3およびv5は䞀意のシヌドを必芁ずし、ランダムに分散されたIDを生成するため倚くのデヌタ構造で断片化を匕き起こす可胜性がある
  • UUID v4は倚くのデヌタ構造で断片化を匕き起こす可胜性のあるランダム性以倖の情報を提䟛しない

ULIDの特城は、以䞋になりたす。

  • UUIDず128ビットの互換性
  • 1ミリ秒で1.21e+24のナニヌクなULIDを生成可胜
  • 蟞曞順゜ヌトが可胜
  • 36文字のUUIDではなく、26文字の文字列ずしお゚ンコヌドされる
  • Crockfordのbase32を䜿甚しお効率ず可読性を向䞊1文字あたり5ビット
  • 倧文字小文字を区別しない
  • 特殊文字を䜿甚しないため、URLに察しお安党
  • 同じミリ秒を正しく怜出しお扱うため、単調な゜ヌト順ずなる

ULIDのJavaScript実装は、同Organization内にありたす。

GitHub - ulid/javascript: Universally Unique Lexicographically Sortable Identifier

他の蚀語の実装に぀いおは、コミュニティベヌスで行われおおり、以䞋に䞀芧が蚘茉されおいたす。

Universally Unique Lexicographically Sortable Identifier / Implementations in other languages

JavaでのULID実装

ULID仕様に曞かれた「Implementations in other languages」のセクションで、珟時点でのJavaの実装は以䞋の3぀が挙げられおいたす。

sulky/sulky-ulid at master · huxi/sulky · GitHub

GitHub - azam/ulidj: ULID (Universally Unique Lexicographically Sortable Identifier) generator and parser for Java.

GitHub - Lewiscowles1986/jULID: Universally Unique Lexicographically Sortable Identifier ported to Java

「Binary Implementation」にチェックが入っおいるのは、最初のひず぀だけです。

䞀芧に茉っおいないものずしおは、こちらもありたした。

GitHub - 0xShamil/ulid4j: A Java library to generate stupidly fast ULIDs

GitHub - f4b6a3/ulid-creator: A Java library for generating Universally Unique Lexicographically Sortable Identifiers (ULID)

今回は、こちらのSulky ULIDを䜿うこずにしたす。

sulky/sulky-ulid at v8.3.0 · huxi/sulky · GitHub

実はこのモゞュヌル、非垞にシンプルで1クラスしかありたせん。

https://github.com/huxi/sulky/blob/v8.3.0/sulky-ulid/src/main/java/de/huxhorn/sulky/ulid/ULID.java

Sulkyずいうのは、Lilithずいうツヌルで䜿われるラむブラリ矀らしいです。

GitHub - huxi/lilith: Lilith is a Logging- and AccessEvent viewer for Logback, log4j, log4j2 and java.util.logging

環境

今回の環境は、こちら。

$ 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-128-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>de.huxhorn.sulky</groupId>
            <artifactId>de.huxhorn.sulky.ulid</artifactId>
            <version>8.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.23.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

今回の䞻題は、こちらのSulky ULIDですね。

        <dependency>
            <groupId>de.huxhorn.sulky</groupId>
            <artifactId>de.huxhorn.sulky.ulid</artifactId>
            <version>8.3.0</version>
        </dependency>

確認は、テストコヌドで行うこずにしたす。

src/test/java/org/littlewings/ulid/SulkyUlidTest.java

package org.littlewings.ulid;

import java.security.SecureRandom;
import java.time.Duration;
import java.util.Comparator;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import de.huxhorn.sulky.ulid.ULID;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class SulkyUlidTest {
    // ここに、テストを曞く

    void waitFor(Duration waitTime) {
        try {
            TimeUnit.MILLISECONDS.sleep(waitTime.toMillis());
        } catch (InterruptedException e) {
            // no-op
        }
    }
}

Sulky ULIDを詊す

このあたりを芋ながら、Sulky ULIDを詊しおいっおみたす。

Universally Unique Lexicographically Sortable Identifier / Usage

たずは、UUID v4ず比范しながら。

    @Test
    public void gettingStarted() {
        ULID ulid = new ULID();
        String ulidString = ulid.nextULID();

        System.out.printf("ulid = %s%n", ulidString);
        assertThat(ulidString.length()).isEqualTo(26);

        String uuidv4 = UUID.randomUUID().toString();
        System.out.printf("uuid v4 = %s%n", uuidv4);
        assertThat(uuidv4.length()).isEqualTo(36);
    }

ULIDのむンスタンスを䜜成し、ULID#nextULIDでULIDを文字列ずしお取埗したす。

実行結果は、こんな感じですね。

ulid = 01GFDY4P6JT4RAKNGF50Q6A4M4
uuid v4 = 47fb9512-18c2-4be1-82d5-054d02f7b0fe

ULIDは24文字、UUID v4は36文字ですね。

ULIDを耇数回生成する堎合は、䜜成したULIDのむンスタンスを䜿い回すようです。

    @Test
    public void gettingStartedMultiple() {
        ULID ulid = new ULID();

        String ulidString1 = ulid.nextULID();
        waitFor(Duration.ofMillis(100L));

        String ulidString2 = ulid.nextULID();
        waitFor(Duration.ofMillis(100L));

        String ulidString3 = ulid.nextULID();

        System.out.printf("ulid1 = %s%n", ulidString1);
        System.out.printf("ulid2 = %s%n", ulidString2);
        System.out.printf("ulid3 = %s%n", ulidString3);

        assertThat(ulidString1).isNotEqualTo(ulidString2);
        assertThat(ulidString2).isNotEqualTo(ulidString3);
        assertThat(ulidString1).isNotEqualTo(ulidString3);

        assertThat(ulidString1.compareTo(ulidString2)).isLessThan(0);
        assertThat(ulidString2.compareTo(ulidString3)).isLessThan(0);
        assertThat(ulidString1.compareTo(ulidString3)).isLessThan(0);
    }

それぞれ別々のULIDが生成され、曞かれおいた通り順序も䜜成順になっおいたす。

実行結果。

ulid1 = 01GFDY5TV27C3V5TG84VHP31T6
ulid2 = 01GFDY5TY7NNQF19Z2Q8FW7VAY
ulid3 = 01GFDY5V1BMF6CPFN85JC7H1KZ

先頭の郚分は同じ文字になっおいたすね。

が、埅ち時間がしれっず入っおいるように、以䞋のようにするずULIDそのものは異なるものが䜜成されたすが、たたに順序が裏返りたす 。

以䞋のコヌドは、倧小比范のずころで時々倱敗したす。

    @Test
    public void gettingStartedNoWaiting() {
        ULID ulid = new ULID();

        String ulidString1 = ulid.nextULID();
        String ulidString2 = ulid.nextULID();
        String ulidString3 = ulid.nextULID();

        System.out.printf("ulid1 = %s%n", ulidString1);
        System.out.printf("ulid2 = %s%n", ulidString2);
        System.out.printf("ulid3 = %s%n", ulidString3);

        assertThat(ulidString1).isNotEqualTo(ulidString2);
        assertThat(ulidString2).isNotEqualTo(ulidString3);
        assertThat(ulidString1).isNotEqualTo(ulidString3);

        assertThat(ulidString1.compareTo(ulidString2)).isLessThan(0);
        assertThat(ulidString2.compareTo(ulidString3)).isLessThan(0);
        assertThat(ulidString1.compareTo(ulidString3)).isLessThan(0);
    }

もう䜿い方の話に戻したしょう。

ULIDのむンスタンスを䜜成する時に、特に匕数は指定したせんでしたが、内郚的にはSecureRandomが指定されおいたす。

Randomを明瀺的にコンストラクタに䞎えるこずもできたす。

    @Test
    public void constructorArg() {
        ULID ulid = new ULID(new SecureRandom());

        System.out.printf("ulid1 = %s%n", ulid.nextULID());
        System.out.printf("ulid2 = %s%n", ulid.nextULID());
    }

たた、StringにせずにULID.Valueずいう圢で扱うこずもできたす。

    @Test
    public void useValue() {
        ULID ulid = new ULID();

        ULID.Value value1 = ulid.nextValue();
        System.out.printf("ulid1 = %s%n", value1.toString());

        ULID.Value value1Incremented = value1.increment();
        System.out.printf("ulid1 incremented = %s%n", value1Incremented.toString());

        ULID.Value value2 = ulid.nextValue();
        System.out.printf("ulid2 = %s%n", value2.toString());
    }

文字列にするにはULID.Value#toStringで良さそうです。

次のULID.Value#incrementずULID#nextValueの違いはずメ゜ッドを芋おいお思いたしたが、ULID.Value#incrementはひず぀倀を
進めるもののようですね。

ulid1 = 01GFDY5TSHJ9A4C28557YR344T
ulid1 incremented = 01GFDY5TSHJ9A4C28557YR344V
ulid2 = 01GFDY5TSJ3WYG32B3G37ZZ4G0

あずはbyte配列で扱ったり

    @Test
    public void useValueAsBinary() {
        ULID ulid = new ULID();

        ULID.Value value1 = ulid.nextValue();
        assertThat(value1.toBytes()).isInstanceOf(byte[].class);

        ULID.Value value2 = ulid.nextValue();
        assertThat(value2.toBytes()).isInstanceOf(byte[].class);
    }

ULID文字列をパヌスしおULID.Valueにしたりできたす。

    @Test
    public void parse() {
        ULID ulid = new ULID();
        String ulidString = ulid.nextULID();

        ULID.Value ulidValue = ULID.parseULID(ulidString);

        assertThat(ulidValue.toString()).isEqualTo(ulidString);
    }

ですが、通垞はULID#nextULIDを䜿っお盎接Stringを取埗すればよいかなず思いたす。

next〜メ゜ッドを呌び出す際に、longでタむムスタンプ倀を枡しお珟圚時刻ずは異なる倀を指定するこずもできたすが指定しない堎合は
System#currentTimeMillisが䜿われおいたす、今回はパス。

ずりあえず、こんなずころでしょう。

たずめ

Sulky ULIDを䜿っお、JavaでULIDを扱っおみたした。

若干䞍安になるポむントもありたしたが、小さい実装なのでこちらで十分かなず思いたす。小さいラむブラリですしね。

ULIDずいうものがどういうものか、ちゃんず芋る良い機䌚にもなりたした。

QuartzのJobやTriggerなどの定矩の保存先をデヌタベヌスにしおみるMisfire発火しなかったむベントに぀いお少し

これは、なにをしたくお曞いたもの

QuartzのJobの蚭定をデヌタベヌスに保存しおみようかなずいうこずで。

あず、Misfire本来起動すべき時間に実行できなかったJobの扱いに぀いお少し調べお曞いおみたした。

JobやTriggerのなど定矩をデヌタベヌスに保存する

ドキュメントずしおは、こちらですね。JobStoreに぀いおです。

Lesson 9: Job Stores

JobStoreそのものは、QuartsのSchedulerが䜿甚するJobやTrigger、Calenderなどにストレヌゞメカニズムを提䟛するものです。

JobStore (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

なお、JobStore自䜓はQuartzの利甚者が盎接扱うものではありたせん。

Never use a JobStore instance directly in your code.

Quartzの蚭定ファむルでどのJobStoreを䜿甚するかは遞択したすが、その操䜜そのものはQuartzが行いたす。

JobStoreの遞択肢ずしおは、以䞋がありたす。

  • RAMJobStore 
 デヌタの保存先をメモリずするJobStore
  • JDBCJobStoreJobStoreTXたたはJobStoreCMT 
 デヌタの保存先をデヌタベヌスずするJobStore
  • TerracottaJobStore 
 デヌタの保存先をTerracottaサヌバヌずするJobStore

今回のお題は、タむトルどおりJDBCJobStoreにしたす。

JDBCJobStoreは、Oracle、PostgreSQL、MySQL、Microsoft SQL Server、HSQLDB、H2Database、DB2など倚くのデヌタベヌスに察応しお
いるそうです。

䜿甚する時にはQuartzが䜿甚するテヌブルを䜜成しおおく必芁があり、そのSQLはこちらに配眮されおいたす。

https://github.com/quartz-scheduler/quartz/tree/v2.3.2/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore

このディレクトリ内を芋るず、実際にQuartzが察応しおいるデヌタベヌス補品が確認できるず思っお良さそうです。

バヌゞョンなどで利甚するSQLに差があるものもあり、MySQLの堎合はInnoDB甚のものずそうでないものの2皮類がありたす。

https://github.com/quartz-scheduler/quartz/blob/v2.3.2/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql

https://github.com/quartz-scheduler/quartz/blob/v2.3.2/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql.sql

テヌブルの名前は「QRTZ_」ずいう接頭蟞で始たりたすが、これはquartz.propertiesでorg.quartz.jobStore.tablePrefixを蚭定するこずで
倉曎できたす。

たた玹介時にも曞きたしたが、JDBCJobStoreには2皮類あり、トランザクション管理をQuartzに任せる堎合は
JobStoreTXorg.quartz.impl.jdbcjobstore.JobStoreTXを、Java EEサヌバヌのトランザクション管理に任せる堎合は JobStoreCMTorg.quartz.impl.jdbcjobstore.JobStoreCMTを䜿いたす。

この指定は、org.quartz.jobStore.classプロパティで行いたす。

JDBCJobStore関係の蚭定は、こちら。

たた、JDBCJobStoreを䜿甚するためには、この他にDataSourceずDriverDelegateを蚭定する必芁がありたす。

DataSourceはquartz.propertiesで定矩するか、アプリケヌションサヌバヌのDataSourceを䜿うこずになりたす。
蚭定は、こちら。

Configuration Reference

DriverDelegateは、StdJDBCDelegateたたはデヌタベヌス固有のDriverDelegateを䜿甚したす。

PostgreSQLやMicrosoft SQL Serverなど、いく぀かの補品にはDriverDelegateがあるので、JDBCJobStore関係のクラスず合わせお以䞋を
参照しおください。

https://github.com/quartz-scheduler/quartz/tree/v2.3.2/quartz-core/src/main/java/org/quartz/impl/jdbcjobstore

では、JDBCJobStoreに぀いおある皋床芋たので、実際に䜿っおみたいず思いたす。

お題

今回は、Java SE環境でアプリケヌションを䜜成、実行しようず思うのですが、mainメ゜ッドを持ったクラスを以䞋の2皮類䜜成しようず
思いたす。

  • JobずTriggerを登録するだけのクラス
  • Schedulerを起動するだけのクラス

最初にJobずTriggerだけを登録しお、そのアプリケヌション自䜓は終了したす。その埌で起動するクラスは、Schedulerを起動するのみで
最初に登録したJobずTriggerを匕き継ぐこずを確認したす。

デフォルトのRAMJobStoreの堎合は、アプリケヌションが終了するずデヌタが倱われるはずですからね。

泚意点

今回は、クラスタリングは䜿いたせん。

Lesson 11: Advanced (Enterprise) Features

こちらを参照しお、クラスタリングされおいない状態で耇数のスケゞュヌラヌを䜿いたす。

How-To: Using Multiple (Non-Clustered) Schedulers

実際の説明は、゜ヌスコヌドず䞀緒にするこずにしたしょう。

環境

今回の環境は、こちら。

$ 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"

デヌタベヌスには、MySQLを䜿うこずにしたす。

$ mysql --version
mysql  Ver 8.0.30 for Linux on x86_64 (MySQL Community Server - GPL)


mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.30    |
+-----------+
1 row in set (0.00 sec)

MySQLは172.17.0.2で動䜜しおいるものずしたす。デヌタベヌスはpractice、アカりントはkazuhirapasswordで䜜成しおいるものずしたす。

準備

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>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    </dependencies>

MySQLを䜿甚するので、Connector/Jを䟝存関係に加えおありたす。

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>

MySQLにQuartzのテヌブルを䜜成する

最初に、MySQLにQuartzで必芁なテヌブルを䜜成しおおきたしょう。

SQLファむルをcurlで取埗しお、そのたたmysqlコマンドで実行するこずにしたした。

$ curl -sL https://raw.githubusercontent.com/quartz-scheduler/quartz/v2.3.2/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql | mysql -ukazuhira -p practice

出力される情報。

Note (Code 1051): Unknown table 'practice.QRTZ_FIRED_TRIGGERS'
Note (Code 1051): Unknown table 'practice.QRTZ_PAUSED_TRIGGER_GRPS'
Note (Code 1051): Unknown table 'practice.QRTZ_SCHEDULER_STATE'
Note (Code 1051): Unknown table 'practice.QRTZ_LOCKS'
Note (Code 1051): Unknown table 'practice.QRTZ_SIMPLE_TRIGGERS'
Note (Code 1051): Unknown table 'practice.QRTZ_SIMPROP_TRIGGERS'
Note (Code 1051): Unknown table 'practice.QRTZ_CRON_TRIGGERS'
Note (Code 1051): Unknown table 'practice.QRTZ_BLOB_TRIGGERS'
Note (Code 1051): Unknown table 'practice.QRTZ_TRIGGERS'
Note (Code 1051): Unknown table 'practice.QRTZ_JOB_DETAILS'
Note (Code 1051): Unknown table 'practice.QRTZ_CALENDARS'
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.
Warning (Code 1681): Integer display width is deprecated and will be removed in a future release.

SQLファむルもかなり叀いからか、いろいろ譊告が出おいたすが 。

テヌブルはこれだけ䜜成されたした。

mysql> show tables;
+--------------------------+
| Tables_in_practice       |
+--------------------------+
| QRTZ_BLOB_TRIGGERS       |
| QRTZ_CALENDARS           |
| QRTZ_CRON_TRIGGERS       |
| QRTZ_FIRED_TRIGGERS      |
| QRTZ_JOB_DETAILS         |
| QRTZ_LOCKS               |
| QRTZ_PAUSED_TRIGGER_GRPS |
| QRTZ_SCHEDULER_STATE     |
| QRTZ_SIMPLE_TRIGGERS     |
| QRTZ_SIMPROP_TRIGGERS    |
| QRTZ_TRIGGERS            |
+--------------------------+
11 rows in set (0.00 sec)

これで、QuartzのデヌタをMySQLに保存する準備ができたした。

アプリケヌションを䜜成する

では、゜ヌスコヌドを䜜成しおいきたす。

たずは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 message = jobDataMap.getString("message");

        logger.info("[{} / {}] execute job, messege = [{}]", name, group, message);
    }
}

せっかくなので、JobDataMapも䜿うようにしたした。

次に、mainメ゜ッドを持ったクラスを䜜成しおいきたす。が、今回は以䞋の2぀のmainクラスを䜜成するこずにしたわけですが。

  • JobずTriggerを登録するだけのクラス
  • Schedulerを起動するだけのクラス

蚭定ファむルは同じものを䜿うか、別々にするかちょっず考えたした。

結果ずしおはCookbookの非クラスタヌ環境で耇数のSchedulerを扱う方法を曞いおいたので、こちらに習いたした。
同じ名前のスケゞュヌラヌを䜿うなら、耇数のむンスタンスでScheduler#startを呌び出さないこず、ずいうのがポむントのようです。

Never start (scheduler.start()) a non-clustered instance against the same set of database tables that any other instance with the same scheduler name is running (start()ed) against. You may get serious data corruption, and will definitely experience erratic behavior.

How-To: Using Multiple (Non-Clustered) Schedulers

こちらの蚘茉に習い、JobやTriggerを登録するアプリケヌションではScheduler#startは呌び出さず、Schedulerを起動するクラスのみで
Scheduler#startを呌び出すこずにしたす。

  • In “App A” create “Scheduler A” (with config that points it at database tables prefixed with “A”), and invoke start() on “Scheduler A”. Now “Scheduler A” in “App A” will execute jobs scheduled by “Scheduler A” in “App A”
  • In “App A” create “Scheduler B” (with config that points it at database tables prefixed with “B”), and DO NOT invoke start() on “Scheduler B”. Now “Scheduler B” in “App A” can schedule jobs to be ran where “Scheduler B” is started.
  • In “App B” create “Scheduler B” (with config that points it at database tables prefixed with “B”), and invoke start() on “Scheduler B”. Now “Scheduler B” in “App B” will execute jobs scheduled by “Scheduler B” in “App A”.

JobずTriggerを登録するだけのクラス。

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

package org.littlewings.quartz;

import java.util.Set;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;
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 JobDefinitionRegister {
    public static void main(String... args) {
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            JobDetail jobDetail =
                    JobBuilder
                            .newJob(PrintMessageJob.class)
                            .withIdentity("job1", "job-group1")
                            .usingJobData("message", "Hello JDBC Store Job")
                            .build();
            Trigger trigger =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .build();


            scheduler.scheduleJob(jobDetail, Set.of(trigger), true);

            //scheduler.start();  // 呌ばないこず

            scheduler.shutdown(true);
        } catch (SchedulerException e) {
            e.printStackTrace();
        } finally {
            AbandonedConnectionCleanupThread.checkedShutdown();
        }
    }
}

こちらは、JobDetailずTriggerの定矩ずSchedulerぞの登録だけ行い、Scheduler#startは呌び出さないこずがポむントです。

Job自䜓は、3秒に1回実行したす。

続いお、Schedulerを起動するだけのクラス。

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

package org.littlewings.quartz;

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

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;

public class ScheduledRunner {
    public static void main(String... args) {
        Scheduler scheduler = null;

        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.start();

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

                try {
                    scheduler.shutdown(true);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }

            AbandonedConnectionCleanupThread.checkedShutdown();
        }
    }

    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
            }
        }
    }
}

こちらは、本圓にSchedulerを䜜成しおScheduler#startを呌び出すだけです。

            scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.start();

20秒間実行したら、終了するこずにはしおいたす。

Quartzの蚭定ファむルは、こちら。

src/main/resources/quartz.properties

org.quartz.scheduler.instanceName=JdbcJobStoreExampleScheduler

org.quartz.threadPool.threadCount=5

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.dataSource=mysqlds

org.quartz.dataSource.mysqlds.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.mysqlds.URL=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin
org.quartz.dataSource.mysqlds.user=kazuhira
org.quartz.dataSource.mysqlds.password=password
org.quartz.dataSource.mysqlds.maxConnections=7

org.quartz.jobStore.〜の項目がJobStoreの蚭定で、JDBCJobStoreJobStoreTXを䜿うこずを定矩しおいたす。

org.quartz.dataSource.[デヌタ゜ヌス名].〜は、Quartzが䜿甚するHikariCPによるデヌタ゜ヌスの定矩になっおいたす。

JDBCJobStoreずデヌタ゜ヌスの玐付けを行うのが、org.quartz.jobStore.dataSourceですね。

再掲ですが、このあたりの蚭定はこちらです。

Jobを実行するスレッド数ずデヌタ゜ヌスのプヌルサむズの関係ですが、垞にスレッドプヌルのサむズず同じ数のJobを実行しおいる堎合は、
デヌタ゜ヌスのプヌルサむズはスレッド数2くらいにしおおくず良いそうです。

If your Scheduler is busy (i.e. nearly always executing the same number of jobs as the size of the thread pool, then you should probably set the number of connections in the DataSource to be the about the size of the thread pool + 2.

Lesson 9: Job Stores

今回、そんなにJobを実行しないのですが、ずりあえず合わせおおきたした。

これで、アプリケヌションの準備は完了です。

実行しおみる

甚意はできたので、アプリケヌションを実行しおみたす。

たずはJobずTriggerの定矩を行いたしょう。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.JobDefinitionRegister
2022-10-14 01:24:00.130 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
2022-10-14 01:24:00.131 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2022-10-14 01:24:00.132 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2022-10-14 01:24:00.133 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'JdbcJobStoreExampleScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 5 threads.
  Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.

2022-10-14 01:24:00.133 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'JdbcJobStoreExampleScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
2022-10-14 01:24:00.133 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
2022-10-14 01:24:00.161 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  c.m.v.c.i.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> z8kfsxar1mb873f1ynw73x|5f075acb, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> z8kfsxar1mb873f1ynw73x|5f075acb, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 7, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {password=******, user=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
2022-10-14 01:24:00.751 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED shutting down.
2022-10-14 01:24:00.751 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED paused.
2022-10-14 01:24:01.124 [org.littlewings.quartz.JobDefinitionRegister.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED shutdown complete.

この時点で、以䞋のテヌブルにデヌタが入っおいたした。

mysql> select * from QRTZ_JOB_DETAILS;
+------------------------------+----------+------------+-------------+----------------------------------------+------------+------------------+----------------+-------------------+------------------------------------------------------------------------------------------------------------------------------+
| SCHED_NAME                   | JOB_NAME | JOB_GROUP  | DESCRIPTION | JOB_CLASS_NAME                         | IS_DURABLE | IS_NONCONCURRENT | IS_UPDATE_DATA | REQUESTS_RECOVERY | JOB_DATA                                                                                                                     |
+------------------------------+----------+------------+-------------+----------------------------------------+------------+------------------+----------------+-------------------+------------------------------------------------------------------------------------------------------------------------------+
| JdbcJobStoreExampleScheduler | job1     | job-group1 | NULL        | org.littlewings.quartz.PrintMessageJob | 0          | 0                | 0              | 0                 | 0x230A23467269204F63742031342030313A32343A3030204A535420323032320A6D6573736167653D48656C6C6F204A4442432053746F7265204A6F620A |
+------------------------------+----------+------------+-------------+----------------------------------------+------------+------------------+----------------+-------------------+------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select * from QRTZ_SIMPLE_TRIGGERS;
+------------------------------+--------------+----------------+--------------+-----------------+-----------------+
| SCHED_NAME                   | TRIGGER_NAME | TRIGGER_GROUP  | REPEAT_COUNT | REPEAT_INTERVAL | TIMES_TRIGGERED |
+------------------------------+--------------+----------------+--------------+-----------------+-----------------+
| JdbcJobStoreExampleScheduler | trigger1     | trigger-group1 |           -1 |            3000 |               0 |
+------------------------------+--------------+----------------+--------------+-----------------+-----------------+
1 row in set (0.00 sec)

mysql> select * from QRTZ_TRIGGERS;
+------------------------------+--------------+----------------+----------+------------+-------------+----------------+----------------+----------+---------------+--------------+---------------+----------+---------------+---------------+--------------------+
| SCHED_NAME                   | TRIGGER_NAME | TRIGGER_GROUP  | JOB_NAME | JOB_GROUP  | DESCRIPTION | NEXT_FIRE_TIME | PREV_FIRE_TIME | PRIORITY | TRIGGER_STATE | TRIGGER_TYPE | START_TIME    | END_TIME | CALENDAR_NAME | MISFIRE_INSTR | JOB_DATA           |
+------------------------------+--------------+----------------+----------+------------+-------------+----------------+----------------+----------+---------------+--------------+---------------+----------+---------------+---------------+--------------------+
| JdbcJobStoreExampleScheduler | trigger1     | trigger-group1 | job1     | job-group1 | NULL        |  1665678240135 |             -1 |        5 | WAITING       | SIMPLE       | 1665678240135 |        0 | NULL          |             0 | 0x                 |
+------------------------------+--------------+----------------+----------+------------+-------------+----------------+----------------+----------+---------------+--------------+---------------+----------+---------------+---------------+--------------------+
1 row in set (0.00 sec)

次に、Schedulerを起動する偎のプログラムを実行したす。

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

Jobが起動し、3秒おきに実行されたす。

2022-10-14 01:25:00.007 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
2022-10-14 01:25:00.031 [org.littlewings.quartz.ScheduledRunner.main()] INFO  c.m.v.c.i.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> z8kfsxar1mb9har148z9s0|1c053ab6, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> z8kfsxar1mb9har148z9s0|1c053ab6, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 7, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {password=******, user=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
2022-10-14 01:25:00.499 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2022-10-14 01:25:00.513 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Handling 1 trigger(s) that missed their scheduled fire-time.
2022-10-14 01:25:00.546 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2022-10-14 01:25:00.546 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2022-10-14 01:25:00.548 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2022-10-14 01:25:00.549 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2022-10-14 01:25:00.616 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED started.
2022-10-14 01:25:03.236 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:06.208 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:09.184 [JdbcJobStoreExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:12.205 [JdbcJobStoreExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:15.446 [JdbcJobStoreExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:18.167 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:21.167 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:21.624 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED shutting down.
2022-10-14 01:25:21.624 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED paused.
2022-10-14 01:25:22.241 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED shutdown complete.

JobDataMapから倀も取埗できおいたすね。

少し時間を空けお、もう1床動かしおみたす。

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

するず、動くには動いたのですが、実行回数が劙に倚いです。

2022-10-14 01:25:52.013 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
2022-10-14 01:25:52.046 [org.littlewings.quartz.ScheduledRunner.main()] INFO  c.m.v.c.i.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> z8kfsxar1mbaleo1u18s6j|1c053ab6, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> z8kfsxar1mbaleo1u18s6j|1c053ab6, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 7, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {password=******, user=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
2022-10-14 01:25:52.566 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2022-10-14 01:25:52.583 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2022-10-14 01:25:52.583 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2022-10-14 01:25:52.585 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2022-10-14 01:25:52.587 [org.littlewings.quartz.ScheduledRunner.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2022-10-14 01:25:52.589 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED started.
2022-10-14 01:25:52.699 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:52.777 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:52.853 [JdbcJobStoreExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:52.949 [JdbcJobStoreExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.019 [JdbcJobStoreExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.085 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.177 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.253 [JdbcJobStoreExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.343 [JdbcJobStoreExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.411 [JdbcJobStoreExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:54.190 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:57.165 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:26:00.170 [JdbcJobStoreExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:26:03.171 [JdbcJobStoreExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:26:06.173 [JdbcJobStoreExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:26:09.224 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:26:12.161 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:26:13.597 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED shutting down.
2022-10-14 01:26:13.597 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED paused.
2022-10-14 01:26:13.748 [org.littlewings.quartz.ScheduledRunner.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler JdbcJobStoreExampleScheduler_$_NON_CLUSTERED shutdown complete.

止たっおいた時間で動いおいるはずだった分が、動いおいる感じがしたす。

2022-10-14 01:25:52.699 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:52.777 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:52.853 [JdbcJobStoreExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:52.949 [JdbcJobStoreExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.019 [JdbcJobStoreExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.085 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.177 [JdbcJobStoreExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.253 [JdbcJobStoreExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.343 [JdbcJobStoreExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:53.411 [JdbcJobStoreExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]
2022-10-14 01:25:54.190 [JdbcJobStoreExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [job1 / job-group1] execute job, messege = [Hello JDBC Store Job]

これは、JobずTriggerを登録しおからSchedulerを実行するたでの間、本来起動すべきだったJob
Misfire発火しなかったむベントに
察しお再実行しおいるからのようですね。

Misfire発火しなかったむベントに぀いお

Misfire発火しなかったむベントに぀いおは、こちらに蚘茉がありたす。

Another important property of a Trigger is its “misfire instruction”. A misfire occurs if a persistent trigger “misses” its firing time because of the scheduler being shutdown, or because there are no available threads in Quartz’s thread pool for executing the job. The different trigger types have different misfire instructions available to them. By default they use a ‘smart policy’ instruction - which has dynamic behavior based on trigger type and configuration. When the scheduler starts, it searches for any persistent triggers that have misfired, and it then updates each of them based on their individually configured misfire instructions.

Lesson 4: More About Triggers

Misfireに察しおどのように振る舞うかは、Triggerの皮類ごずに指定できる内容も倉化するようです。

デフォルトでは、Triggerの皮類ず構成によっお動的に振る舞う「smart policy」ずいうものが適甚されおいるそうです。

Misfireに察する指瀺はTriggerごずに定矩があり、以䞋のクラス、むンタヌフェヌスに定矩がありたす。

実際には、こちらのScheduleBuilderを䜿っお指定するこずになりたすが。

このうち、SimpleTriggerおよびSimpleScheduleBuilderに関しおは、以䞋に䟋がありたす。

Example 5 - Job Misfires

今回はSimpleTriggerを䜿い、氞遠に実行するようにしおいるREPEAT_INDEFINITELYので、
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNTが遞択されおいるようです。

        if (instr == Trigger.MISFIRE_INSTRUCTION_SMART_POLICY) {
            if (getRepeatCount() == 0) {
                instr = MISFIRE_INSTRUCTION_FIRE_NOW;
            } else if (getRepeatCount() == REPEAT_INDEFINITELY) {
                instr = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
            } else {
                // if (getRepeatCount() > 0)
                instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
            }

https://github.com/quartz-scheduler/quartz/blob/v2.3.2/quartz-core/src/main/java/org/quartz/impl/triggers/SimpleTriggerImpl.java#L473-L481

こちらは次回の起動時、その次に起動しおいたはずの回数だけJobを実行するようにする蚭定のようです。

SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

それで、アプリケヌション自䜓が停止しおいお、Jobが起動するはずだった実行回数を無芖するような蚭定をしお詊しおみようかなず
思ったのですが、SimpleTriggerではそういったものはなさそうでした。

他のTriggerを芋る時に、もっず远っおみるずしたす 。

たずめ

今回は、QuartzのJobやTriggerなどの定矩をデヌタベヌスに保存するようにしおみたした。

JDBCJobStoreやDataSourceなどの蚭定の読み方、クラスタヌずしない堎合の泚意事項など、ちょっずドキュメントを远うのに苊劎した
ずころはありたすが、䜿っおみるずそうハマるものではなかったですね。

䞀方で、付随しお芋぀けたMisfireに関しおはただ挙動がよくわからないので、たた別途芋おいきたいなずも思いたす。