「spring-boot-starter-〇〇」の有難みが実感しにくい...

f:id:ts0818:20211129073303j:plain

www.itmedia.co.jp

 英政府競争規制当局の競争・市場庁(CMA)は11月26日(現地時間)、1月から独禁法違反の可能性を調査中の米Googleの「プライバシーサンドボックス」の取り組みについて、Googleから改善したコミットメントを受理したと発表した。

Google、「プライバシーサンドボックス」で独立監視者任命を約束 - ITmedia NEWS

 Googleは今年の3月に、ChromeブラウザでのサードパーティーCookieの完全廃止とプライバシーサンドボックス採用に向けた計画を発表したが、6月にはCMAや欧州委員会による調査に協力するため、その実行を2023年後半まで延期すると発表した。

Google、「プライバシーサンドボックス」で独立監視者任命を約束 - ITmedia NEWS

⇧ とあって、

www.itmedia.co.jp

 “他のプロバイダーのように”サードパーティーCookieの代わりにユーザーを一意に識別するような識別子を使うことはしないとGoogleは強調する。Googleが採用するのは、プライバシーサンドボックスをベースとするFLoC(Federated Learning of Cohorts、群れの連合学習)と呼ぶAPI。FLoCは、大まかに言うと、同じ関心を持つ匿名ユーザーの集合体という意味で、名称のFLoCもflock(群れ)を意図しているようだ。このAPIにより、個々のユーザーを追跡せずに広告主とパブリッシャーに成果を提供することが可能という。

Google、サードパーティーCookie完全廃止に向けてFLoCのテスト開始 - ITmedia NEWS

⇧「FLoC(Federate Learning of Cohorts)」が結構、評判が良くなかったような...

システム開発とかに影響してくるかが気になるところですかね...

今回は、「spring-boot-starter-〇〇」と、「spring-boot-starter-quartz」について調べてみました。

レッツトライ~。

 

「spring-boot-starter-〇〇」の有難みが実感しにくい...

「spring-boot-starter-quartz」って「依存関係」を利用した際に、結局のところ、

  • spring framework」側ではどこまで面倒を見てくれるのか?
  • 利用する側がどこまで面倒を見ないといけないか?

全くもってよく分からなかったので、「spring-boot-starter-〇〇」について確認してみました。

そして、「Qiita」で、「quartz spring」で検索したら、2件しかヒットせず、しかも「データベース」と連携してるものは、0件というね...

情報が少な過ぎて、辛い...

とりあえず、「spring-boot-starter-〇〇」の情報を確認すると、

github.com

Spring Boot Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need without having to hunt through sample code and copy paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access include the spring-boot-starter-data-jpa dependency in your project, and you are good to go.

https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-starters

⇧ ってあるところを見ると、「Spring」関連の技術が洩れなく利用できる状態になるってことらしい。

つまり「spring framework」で「依存性の注入(DI:Dependency Injection)」を実現してくれる仕組みなんかも含まれてるんかな?と思ったら、

docs.spring.io

What is in a name

All official starters follow a similar naming pattern; spring-boot-starter-*, where * is a particular type of application. This naming structure is intended to help when you need to find a starter. The Maven integration in many IDEs lets you search dependencies by name. For example, with the appropriate Eclipse or Spring Tools plugin installed, you can press ctrl-space in the POM editor and type “spring-boot-starter” for a complete list.

https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-starters

⇧ っていう説明で、

一応、「spring-boot-starter-*」の一覧も掲載してくれてるっぽいんですが、

docs.spring.io

Table 1. Spring Boot application starters
Name Description

spring-boot-starter

Core starter, including auto-configuration support, logging and YAML

spring-boot-starter-activemq

Starter for JMS messaging using Apache ActiveMQ

spring-boot-starter-amqp

Starter for using Spring AMQP and Rabbit MQ

spring-boot-starter-aop

Starter for aspect-oriented programming with Spring AOP and AspectJ

spring-boot-starter-artemis

Starter for JMS messaging using Apache Artemis

spring-boot-starter-batch

Starter for using Spring Batch

spring-boot-starter-cache

Starter for using Spring Framework’s caching support

spring-boot-starter-data-cassandra

Starter for using Cassandra distributed database and Spring Data Cassandra

spring-boot-starter-data-cassandra-reactive

Starter for using Cassandra distributed database and Spring Data Cassandra Reactive

spring-boot-starter-data-couchbase

Starter for using Couchbase document-oriented database and Spring Data Couchbase

spring-boot-starter-data-couchbase-reactive

Starter for using Couchbase document-oriented database and Spring Data Couchbase Reactive

spring-boot-starter-data-elasticsearch

Starter for using Elasticsearch search and analytics engine and Spring Data Elasticsearch

spring-boot-starter-data-jdbc

Starter for using Spring Data JDBC

spring-boot-starter-data-jpa

Starter for using Spring Data JPA with Hibernate

spring-boot-starter-data-ldap

Starter for using Spring Data LDAP

spring-boot-starter-data-mongodb

Starter for using MongoDB document-oriented database and Spring Data MongoDB

spring-boot-starter-data-mongodb-reactive

Starter for using MongoDB document-oriented database and Spring Data MongoDB Reactive

spring-boot-starter-data-neo4j

Starter for using Neo4j graph database and Spring Data Neo4j

spring-boot-starter-data-r2dbc

Starter for using Spring Data R2DBC

spring-boot-starter-data-redis

Starter for using Redis key-value data store with Spring Data Redis and the Lettuce client

spring-boot-starter-data-redis-reactive

Starter for using Redis key-value data store with Spring Data Redis reactive and the Lettuce client

spring-boot-starter-data-rest

Starter for exposing Spring Data repositories over REST using Spring Data REST

spring-boot-starter-freemarker

Starter for building MVC web applications using FreeMarker views

spring-boot-starter-groovy-templates

Starter for building MVC web applications using Groovy Templates views

spring-boot-starter-hateoas

Starter for building hypermedia-based RESTful web application with Spring MVC and Spring HATEOAS

spring-boot-starter-integration

Starter for using Spring Integration

spring-boot-starter-jdbc

Starter for using JDBC with the HikariCP connection pool

spring-boot-starter-jersey

Starter for building RESTful web applications using JAX-RS and Jersey. An alternative to spring-boot-starter-web

spring-boot-starter-jooq

Starter for using jOOQ to access SQL databases with JDBC. An alternative to spring-boot-starter-data-jpa or spring-boot-starter-jdbc

spring-boot-starter-json

Starter for reading and writing json

spring-boot-starter-jta-atomikos

Starter for JTA transactions using Atomikos

spring-boot-starter-mail

Starter for using Java Mail and Spring Framework’s email sending support

spring-boot-starter-mustache

Starter for building web applications using Mustache views

spring-boot-starter-oauth2-client

Starter for using Spring Security’s OAuth2/OpenID Connect client features

spring-boot-starter-oauth2-resource-server

Starter for using Spring Security’s OAuth2 resource server features

spring-boot-starter-quartz

Starter for using the Quartz scheduler

spring-boot-starter-rsocket

Starter for building RSocket clients and servers

spring-boot-starter-security

Starter for using Spring Security

spring-boot-starter-test

Starter for testing Spring Boot applications with libraries including JUnit Jupiter, Hamcrest and Mockito

spring-boot-starter-thymeleaf

Starter for building MVC web applications using Thymeleaf views

spring-boot-starter-validation

Starter for using Java Bean Validation with Hibernate Validator

spring-boot-starter-web

Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container

spring-boot-starter-web-services

Starter for using Spring Web Services

spring-boot-starter-webflux

Starter for building WebFlux applications using Spring Framework’s Reactive Web support

spring-boot-starter-websocket

Starter for building WebSocket applications using Spring Framework’s WebSocket support

⇧「spring-boot-starter」の扱いがよく分かりませんと。

「spring-boot-starter」と「spring-boot-starter-*」の関係がいまいちよう分からんのですよ。

つまり、「spring-boot-starter-*」は「spring-boot-starter」の機能を受け継いでるのか、それとも、「spring-boot-starter-*」を読み込んだだけでは「spring-boot-starter」の機能は使えないのか、って説明が無いので解釈が難しいところですが、特に「spring-boot-starter-*」に「spring-boot-starter」の機能が含まれるとは記載されていないのでモヤモヤ感が半端ない...。

というわけで、試しに「依存関係」を見比べてみた。

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-starters/spring-boot-starter/build.gradle

plugins {
	id "org.springframework.boot.starter"
}

description = "Core starter, including auto-configuration support, logging and YAML"

dependencies {
	api(project(":spring-boot-project:spring-boot"))
	api(project(":spring-boot-project:spring-boot-autoconfigure"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-logging"))
	api("jakarta.annotation:jakarta.annotation-api")
	api("org.springframework:spring-core")
	api("org.yaml:snakeyaml")
}    

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-starters/spring-boot-starter-quartz/build.gradle

plugins {
	id "org.springframework.boot.starter"
}

description = "Starter for using the Quartz scheduler"

dependencies {
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
	api("org.springframework:spring-context-support")
	api("org.springframework:spring-tx")
	api("org.quartz-scheduler:quartz")
} 

⇧ どうやら、「spring-boot-starter-*」は「spring-boot-starter」の「依存関係」を引き継いでるっぽい雰囲気なんだけど、「build.gradle」の8行目の書きっぷりがどういうことをしてくれてるのかがよく分からんけども...

 

spring-boot-starter-quartzを試してみる

Eclipseを起動して、「ファイル(F)」>「新規(N)」>「その他(O)...」を選択。

f:id:ts0818:20211128135736p:plain

「Spring Boot」>「Spring スターター・プロジェクト」を選択し、「次へ(N)>」を押下。

f:id:ts0818:20211128135822p:plain

「名前」を適当に入力し、「型:」は「Gradle」にしてます。「次へ(N)>」を押下。

f:id:ts0818:20211128135927p:plain

追加した「依存関係」は、以下のキャプチャ画像の通り。「次へ(N)>」を押下。

f:id:ts0818:20211128140133p:plain

「完了(F)>」を押下。

f:id:ts0818:20211128140205p:plain

「ビルドツール」が「Gradle」の「Spring Boot」のプロジェクトが作成されました。

f:id:ts0818:20211128140838p:plain

「build.gradle」を確認してみる。

plugins {
	id 'org.springframework.boot' version '2.6.0'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-quartz'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.postgresql:postgresql'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	useJUnitPlatform()
}

⇧ ってな感じです。(「Spring Boot」の実行でエラーが出てしまったので、後で、「依存関係」追加してます。追加した「依存関係」を含めた「build.gradle」は後述で掲載。)

で、「quartz」でデータベース」を使う場合は、「quartz」用のテーブルを作成する必要があるらしく、

github.com

⇧ 上記で公開されてる「table-〇〇.sql」の中から自分の環境で利用してる「データベース」のものを探して、ダウンロードして実施すれば良いらしい。

自分のローカル環境の「PostgreSQL」で試した限りでは、「qrtz_〇〇」って「テーブル」が作成される感じ。

f:id:ts0818:20211128155914p:plain

「qrtz_〇〇」って「テーブル」が何に使われるかと言うと、

examples.javacodegeeks.com

The RAMJobStore is the default JobStore which utilizes RAM as its storage device. The ramification of this is that access is extremely fast, but the data is completely volatile – therefore this JobStore should not be used if true persistence between program shutdowns is required. The JobStoreSupport contains base functionality for JDBC-based JobStore implementations.

https://examples.javacodegeeks.com/enterprise-java/quartz/java-quartz-architecture-example/

⇧上記サイト様によりますと、「Job」を保存しておくために「Job Store」が利用されるらしいのですが、「RAM」だと仮に「アプリケーション」が稼働してる「マシン」が停止してしまうと「データ」が消えてしまうので、「データ」を「永続化」するためには「JDBC」を使えば良い、ってことで「データベース」を利用して「Job」を保存するために「qrtz_〇〇」って「テーブル」が必要になるってことらしい。

実際には、

www.javarticles.com

⇧ 上図のようなクラスが必要になってくるみたいね。

実装に入る前に、「qrtz_〇〇」テーブル用の設定が必要になってくるらしく、「quartz.properties」もしくは、「application.properties」に設定を書く必要があるそうな。「application.properties」のほうには、

spring.pleiades.io

⇧「spring.quartz.〇〇」な設定で必要そうなものを書けば良いらしいとは思うんだけど、ネットのexampleでほとんど設定してる人が見当たらない気がする...

そして、「quartz.properties」のほうはと言うと、「Quartz」のドキュメントによると、

Configuration of Quartz is typically done through the use of a properties file, in conjunction with the use of StdSchedulerFactory (which consumes the configuration file and instantiates a scheduler).

https://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html

By default, StdSchedulerFactory load a properties file named quartz.properties from the "current working directory". If that fails, then the quartz.properties file located (as a resource) in the org/quartz package is loaded. If you wish to use a file other than these defaults, you must define the system property org.quartz.properties to point to the file you want.

https://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html

⇧ デフォルトだと「StdSchedulerFactory」が「properties file」を読み込むらしいんだけど、そもそも、「StdSchedulerFactory」が何なのか不明なんですけどね...

www.quartz-scheduler.org

An implementation of SchedulerFactory that does all of its work of creating a QuartzScheduler instance based on the contents of a Properties file.

https://www.quartz-scheduler.org/api/2.1.7/org/quartz/impl/StdSchedulerFactory.html

⇧ ってあって、「SchedulerFactory」って「インターフェイス」を継承してるってことなんだけど、

www.quartz-scheduler.org

⇧「SchedulerFactory」って「インターフェイス」を継承してるクラスとして「DirectSchedulerFactory」ってものも存在するそうな。

じゃあ「SchedulerFactory」って「インターフェイス」ってのはというと、

www.quartz-scheduler.org

Provides a mechanism for obtaining client-usable handles to Scheduler instances.

https://www.quartz-scheduler.org/api/2.1.7/org/quartz/impl/DirectSchedulerFactory.html

⇧ ってあることから、この「インターフェイス」が無いと「Quartz」が始まらないということらしい。

まぁ、基本的には「SchedulerFactory」を使っていけば良いってことですかね。

で、「Quartz」側の準備ができたら、「Spring Boot」側で「Quartz」が認識されるようにしてあげないといけないらしい。

spring.pleiades.io

Quartz Schedulerを作成および構成し、そのライフサイクルを Spring アプリケーションコンテキストの一部として管理し、依存性注入のための Bean 参照としてスケジューラを公開する FactoryBean 

https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html 

⇧ っていうか、「spring-boot-starter-quartz」を「依存関係」に追加したら、すぐ動くんじゃなかったんか...

なので、「Spring Boot」で「Quartz」を使う場合は、

f:id:ts0818:20211128200130p:plain

最低、赤枠のクラスは最低限必要になってくるんかな、ネット上のexampleを見た感じだと。あと、上図の赤枠で追加できてないけど、実行クラスが必要ですかね。

で、肝心の実行クラスについては、「@Controller」や「@RestController」の付いたクラス経由ってexampleが多いんですが、

medium.com

⇧ 上記サイト様が「org.springframework.boot.ApplicationRunner」という「インターフェイス」を利用して、実行してたので、「オフライン」でも試せそうです。

そもそも、「バッチ処理」って「オフライン」が一般的だと思いますし(勝手なイメージ)。

で、何やかんやで、エラーとかも出て、「build.gradle」に「c3p0」ってのを追加したりもしてますが、ファイルの構成は以下のようになりました。

f:id:ts0818:20211128225539p:plain

使用した「テーブル」は、「PostgreSQL」に「learning」って「スキーマ」を作って、そこに作成してた「user」テーブルを利用してます。

f:id:ts0818:20211128225657p:plain

■テーブルの構造

f:id:ts0818:20211128230202p:plain

■制約

f:id:ts0818:20211128230228p:plain

というわけで、設定は以下のような感じに。

■/quartz-boot/build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.0'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-quartz'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.postgresql:postgresql'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// https://mvnrepository.com/artifact/com.mchange/c3p0
	implementation group: 'com.mchange', name: 'c3p0', version: '0.9.5.5'
}

test {
	useJUnitPlatform()
}

■/quartz-boot/src/main/resources/application.properties

# PsogreSQLを利用するための設定
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5434/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres

# SQL文とかのデバッグログの表示とか
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
#hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
#hibernate.globally_quoted_identifiers_skip_column_definitions=true

## QuartzProperties
spring.quartz.job-store-type = jdbc
spring.quartz.properties.org.quartz.threadPool.threadCount = 5

#Quartz Log level
logging.level.org.springframework.scheduling.quartz=DEBUG
logging.level.org.quartz=DEBUG

■/quartz-boot/src/main/resources/quartz.properties

#============================================================================
# Configure Main Scheduler Properties
#============================================================================

org.quartz.scheduler.instanceName: TestScheduler
org.quartz.scheduler.instanceId: instance_one

org.quartz.scheduler.skipUpdateCheck: true

#============================================================================
# Configure ThreadPool
#============================================================================

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 5
org.quartz.threadPool.threadPriority: 5

#============================================================================
# Configure JobStore
#============================================================================

org.quartz.jobStore.misfireThreshold: 60000

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.dataSource=myDS
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true

#============================================================================
# Other Example Delegates
#============================================================================
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v6Delegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v7Delegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DriverDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.MSSQLDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PointbaseDelegate
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.WebLogicDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate

#============================================================================
# Configure Datasources
#============================================================================

org.quartz.dataSource.myDS.driver: org.postgresql.Driver
org.quartz.dataSource.myDS.URL: jdbc:postgresql://localhost:5434/postgres
org.quartz.dataSource.myDS.user: postgres
org.quartz.dataSource.myDS.password: postgres
org.quartz.dataSource.myDS.maxConnections: 5
org.quartz.dataSource.myDS.validationQuery: select 0

#============================================================================
# Configure Plugins
#============================================================================

#org.quartz.plugin.shutdownHook.class: org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownHook.cleanShutdown: true


#org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingJobHistoryPlugin    

そんで、ソースコードは以下のような感じに。

■/quartz-boot/src/main/java/com/example/demo/batch/config/AutowiringSpringBeanJobFactory.java

package com.example.demo.batch.config;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory
implements ApplicationContextAware{

    AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

■/quartz-boot/src/main/java/com/example/demo/batch/config/QuartzConfig.java

package com.example.demo.batch.config;

import java.io.IOException;
import java.util.Properties;

import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfig {

    @Autowired
    ApplicationContext applicationContext;

    @Bean
    public JobFactory jobFactory() {
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {

        SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
        schedulerFactory.setQuartzProperties(quartzProperties());
        schedulerFactory.setWaitForJobsToCompleteOnShutdown(true);
        schedulerFactory.setAutoStartup(true);
        schedulerFactory.setJobFactory(jobFactory());
        return schedulerFactory;
    }

    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
}

■/quartz-boot/src/main/java/com/example/demo/entity/UserEntity.java

package com.example.demo.entity;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

@Data
@Entity
@Table(name = "user",  schema="learning")
public class UserEntity {

	@Id
	private Integer id;
	private String name;

}

■/quartz-boot/src/main/java/com/example/demo/repository/UserRepository.java

package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.entity.UserEntity;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {

}    

■/quartz-boot/src/main/java/com/example/demo/batch/service/SchedulerService.java

package com.example.demo.batch.service;

import org.springframework.stereotype.Service;

@Service
public interface SchedulerService {

	void startAllSchedulers();

}    

■/quartz-boot/src/main/java/com/example/demo/batch/service/impl/SchedulersServiceImpl.java

package com.example.demo.batch.service.impl;

import java.util.TimeZone;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;

import com.example.demo.batch.job.UserJob;
import com.example.demo.batch.service.SchedulerService;

@Service
public class SchedulersServiceImpl implements SchedulerService {
	@Autowired
	private SchedulerFactoryBean schedulerFactoryBean;

	@Override
	public void startAllSchedulers() {
		//
		Scheduler scheduler = schedulerFactoryBean.getScheduler();

		JobDetail jobDetail = JobBuilder.newJob(UserJob.class)
				.withIdentity("userJob", "jobGroup1")
				.storeDurably(true)
				.build();

		CronTrigger trigger = TriggerBuilder
				.newTrigger()
				.withIdentity("cronTrigger1", "triggerGroup1")
				.withSchedule(
						CronScheduleBuilder.cronSchedule("*/1 * * * * ?")
								.inTimeZone(TimeZone.getTimeZone("Asia/Tokyo")))
				.startNow().build();

		try {
			// org.quartz.ObjectAlreadyExistsExceptionの例外を回避する
			if (scheduler.checkExists(jobDetail.getKey())){
			    scheduler.deleteJob(jobDetail.getKey());
			}		
		
			scheduler.scheduleJob(jobDetail, trigger);
		} catch (SchedulerException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
	}
}

■/quartz-boot/src/main/java/com/example/demo/batch/job/UserJob.java

package com.example.demo.batch.job;

import java.util.List;
import java.util.Objects;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

import com.example.demo.entity.UserEntity;
import com.example.demo.repository.UserRepository;

public class UserJob implements Job {
	@Autowired
	private UserRepository userRepository;

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		// TODO 自動生成されたメソッド・スタブ
        List<UserEntity> userEntityList = userRepository.findAll();
        if(Objects.nonNull(userEntityList)) {
        	userEntityList.stream()
        	.forEach(userEntity -> {
        		if ("鈴木".contains(userEntity.getName())) {
        			userEntity.setName("ミスターX");
        			userRepository.save(userEntity);
        		}
        	});
        }
	}
}    

■/quartz-boot/src/main/java/com/example/demo/batch/SchedulerStartUpHundler.java

package com.example.demo.batch;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import com.example.demo.batch.service.SchedulerService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class SchedulerStartUpHundler implements ApplicationRunner {

	@Autowired
	private SchedulerService schedulerService;

	@Override
	public void run(ApplicationArguments args) throws Exception {
		//
		log.info("Schedule all new scheduler jobs at app startup - starting");
		schedulerService.startAllSchedulers();
		log.info("Schedule all new scheduler jobs at app startup - complete");
	}
}

■/quartz-boot/src/main/java/com/example/demo/QuartzBootApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class QuartzBootApplication {

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

⇧ ってな感じで、「バッチ処理」の「Job」としては、「learning.user」テーブルの「name」カラムの値が「鈴木」ってレコードがあったら、「鈴木」を「ミスターX」に更新するという、全く実用的ではないサンプルなので、このあたりは、ご自分の用途に合わせていただければと。

「learning.user」テーブルのデータに「鈴木」を含めて、

f:id:ts0818:20211128232130p:plain

Eclipseのほうで、実行してみます。

f:id:ts0818:20211128232250p:plain

起動して、しばらくしたら、「コンソール」タブの右のほうにあるf:id:ts0818:20211128232459p:plain を押下で停止します。

f:id:ts0818:20211128232328p:plain

f:id:ts0818:20211128232406p:plain

「テーブル」を更新してみると、

f:id:ts0818:20211128232615p:plain

「鈴木」から「ミスターX」に更新されたので、上手くいったようです。

f:id:ts0818:20211128232647p:plain

ただ、「Spring Boot」の「ApplicationRunner」なのか「Quartz」なのか分からんけど、「プロセス」が終了してないっぽいので、「タスクマネージャー」から「java.exe」の「タスク終了(E)」して、一度Eclipseも閉じました。(「Java仮想マシンJVMJava Virtual Machine)」が停止すれば、「Java」の「プロセス」も終了するかなと思い)

f:id:ts0818:20211128233114p:plain

というわけで、「Spring Boot」の「ApplicationRunner」を利用するって方法に辿り着くまでに時間がかかって、肝心の「Quartz」の「JobDetail」とか「Tigger」のコーディングがいろいろ試せなかったというね...

しかも、「Spring Boot」の「ApplicationRunner」の「プロセス」の制御方法が分からんし...

あと、「qrtz_〇〇」テーブルに登録される「Trigger」とかが、前回登録されてたのが消されちゃってるので、「application.properties」か「quartz.properties」かのどっちかに何かしら設定をする必要があるんかな?

2022年2月13日(日)追記:↓ ここから

「spring-boot-autoconfigure」の依存関係を追加すれば、「SchedulerFactoryBean」クラスを継承したクラスを作成しなくてもQuartzの機能を使えました。

ts0818.hatenablog.com

⇧ 手前味噌で恐縮ですが、上記の記事でまとめております。

それにしても、「spring-boot-autoconfigure」を依存関係に追加してQuartzを使ってる情報がネットで皆無だったのが気になりますな...

2022年2月13日(日)追記:↑ ここまで

今回もモヤモヤ感が半端ない...

今回はこのへんで。