Spring BootでQuartzを利用する（１）

Spring Bootにジョブスケジューラの『Quartz』を組込み
ジョブ登録と削除用エンドポイントを作成し実行します。


## ■完成イメージ
    +----------+            +-------------+            +------------+
    |          |            | Spring Boot |   JDBC     |   Job      |
    | Browser　|<---------->|     +       |<---------->| Persistent |
    |          | port=8080  |   Quartz    | port=33060 |   data     |
    |          |            |             |            |  (MySQL)   |
    +----------+            +-------------+            +------------+

## ■構築環境
・Windows10 バージョン21H2
・IntelliJ IDEA 2022.1.4(Community Edition)
・OpenJDK Runtime Environment Zulu17.36+17-CA (build 17.0.4.1+1-LTS)
・Spring Boot 2.7.4
・Quartz
・Mysql 5.7.39

## 1. プロジェクトへQuartzを組込む
build.geadleを編集しJPA, JDBC Driver, Quartzの依存関係を追加。
```build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-quartz'
	runtimeOnly 'mysql:mysql-connector-java'
}
```

## 2. Spring Bootアプリケーションのパラメータを設定
エンドポイントの受信ポート及びMysqlとの接続設定を追加。
spring.quartz.jdbc.initialize-schemaをalwaysにする事でQuartzに必要なテーブルを自動作成します。
ただし起動時に毎回初期化が走るため、不必要であればneverに変更してください。
またMySQLユーザにはテーブルを作成する適切なロールが紐づいている必要があります。
```application.properties
server.port=8080
spring.datasource.platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://***.***.***.***:33060/データベース名
spring.datasource.username=ユーザ名
spring.datasource.password=パスワード
spring.jpa.open-in-view=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true


#QUARTZ CONFIGS
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=always
```

## 3. Quartzの起動とDIコンテナへの登録
Spring Bootの起動時にQuartzも同時に起動しDIコンテナにインスタンスを登録します。
```SchedulerApplication.java
@SpringBootApplication
public class SchedulerApplication {

	public static void main(String[] args) {
		SpringApplication.run(SchedulerApplication.class, args);
	}

	@Bean
	public Scheduler scheduler(SchedulerFactoryBean factory) throws SchedulerException {
		Scheduler scheduler = factory.getScheduler();
		scheduler.start();
		return scheduler;
	}
}
```

## 4. パラメータクラス作成
ジョブの引数として渡すパラメータクラスを作成。
シリアライズ（直列化）できるようSerializableをimplementsします。
```JobParameter.java
public class JobParameter implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String uid;
    private final String name;
}
```

## ５. ジョブクラスを作成
パラメータとして受け取った名前を標準出力するジョブになります。
```SimpleJob.java
public class SimpleJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        JobDataMap parameter = context.getJobDetail().getJobDataMap();
        JobParameter jobParameter = (JobParameter)parameter.get("parameter");
        System.out.println(MessageFormat.format("JobName: {0}",jobParameter.getName()));
    }
}
```

## 6. エンドポイント作成
登録と削除用のエンドポイントを持つコントローラクラスになります。
```SimpleController.java
@RestController
@RequestMapping("/")
public class SimpleController {

    private final Scheduler scheduler;

    @Autowired
    public SimpleController(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    @GetMapping("/create")
    public ResponseEntity create() throws Exception {
        // パラメータ作成
        JobParameter jobParameter = new JobParameter("0001", "shigemax");

        // ジョブ生成及びパラメータ付与
        JobDetail job = JobBuilder.newJob(SimpleJob.class)
                .withIdentity("sample_job").build();
        job.getJobDataMap().put("parameter", jobParameter);

        // トリガー設定（120秒後に発火）
        Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(120)
                .atZone(ZoneId.systemDefault()).toInstant());
        Trigger trigger = TriggerBuilder.newTrigger()
                .startAt(afterFiveSeconds)
                .build();

        // ジョブ登録
        this.scheduler.scheduleJob(job, trigger);

        return ResponseEntity.ok().build();
    }

    @GetMapping("/delete")
    public ResponseEntity delete() throws Exception {
        // ジョブ名で削除
        this.scheduler.deleteJob(new JobKey("sample_job"));
        return ResponseEntity.ok().build();
    }
}
```

## 7. Spring Boot起動
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v2.7.4)


## 8. Quartz用テーブルの確認
    mysql> SHOW TABLES;
    +--------------------------+
    | Tables_in_quartz         |
    +--------------------------+
    | 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            |
    +--------------------------+


## 9. ジョブの登録
HTTP GETにてhttp://localhost:8080/createにリクエスト。
120秒後に標準出力にてパラメータで引き渡された名前が表示される。
    JobClass: class com.example.scheduler.job.SimpleJob
    JobName: shigemax

## 10. MySQLコンテナにbashログイン
HTTP GETにてhttp://localhost:8080/createにリクエスト後、
HTTP GETにてhttp://localhost:8080/deleteにリクエスト。
ジョブが削除された為、ジョブが120秒たっても実行されないことを確認。

## 参考

https://github.com/shigemax999/sample-spring-boot-plus-quartz