米Googleの親会社である米Alphabetは11月4日(現地時間)、AIを使った創薬事業を手掛ける新会社「Isomorphic Labs」を設立すると発表した。CEOは、遺伝子情報からタンパク質の立体構造を解析するAI「AlphaFold」や、囲碁AI「AlphaGo」を開発したAlphabet傘下の英DeepMind創業者、デミス・ハサビス代表が兼務する。
Google親会社のAlphabet、AI創薬の新会社設立 DeepMind創業者がCEOに - ITmedia NEWS
⇧「創薬事業」にイノベーションが起こるのかしら、何にせよ、良い方向へ進んでいって欲しいですね。
今回は、「バッチ処理」なんかについて調べてみました。
レッツトライ~。
バッチ処理って?
Wikipediaさんに聞いてみた。
⇧ ってな感じの説明なんだけど、
「コンピュータでひとまとまりのデータを一括して処理する方式。本項で詳述する。」って言っており、「バッチ(batch)」てのは、
「バッチ (batch)」という言葉は、プログラマがプログラミングをする際、紙にコードを書き、紙テープか80カラム仕様のパンチカードにパンチしていった時代の言葉。カードまたは紙テープはシステムオペレーターに渡され、オペレーターはタスクのスケジューリングをし、コンピュータにカードまたは紙テープを投入していった。スケジューリングされたタスクは直ちにシステムに入れられたのではなく、一緒にまとめて投入されたので(バッチとは、複数の似ているものを1つのグループとして生産、処理すること、または一緒に集めて1つのユニットとして扱うこと。en:Batch processingより)、これらのタスクの集まりを「バッチジョブ」と呼ぶようになった。
⇧ っていう語源らしい。
英語版のWikipediaによると、
Computerized batch processing is the running of "jobs that can run without end user interaction, or can be scheduled to run as resources permit."
⇧ というように、人手による操作を介さずに実行できる、または、「リソース」の許す限り「スケジュール」された「ジョブ」のことですと。
ちなみに、「Process(computing)」のWikipediaの情報によると、
Program vs. Process vs. Thread
Scheduling, Preemption, Context Switching
⇧「OS(Operation System)」においても「スケジュール」の仕組みが導入されてることが多いらしい。
「job」については出てこないのだけど、「program」の命令が「process」となって、「process」は複数の「thread」で構成されており、「scheduler」が「CPU」に実行させるタイミングを管理してる感じでしょうか。
で、「schedule」については、
In computer, scheduling is the action of assigning resources to perform tasks. The resources may be processors, network links or expansion cards. The tasks may be threads, processes or data flows.
⇧「tasks」を実行するために「リソース」を割り当てるアクションということで、「The tasks may be threads, processes or data flows.」とあるように、「task」は複数の「prosess」「thread」または、「data flow」で構成されてますと。
「scheduler」がどんなところで使われてるかの例としては、
A highly simplified structure of the Linux kernel, which includes process schedulers, I/O schedulers, and packet schedulers
⇧「File System」なんかにおける「I/O」の制御とか、「prosess」の制御とかが有名なんですかね?
Windows環境であれば、「タスク マネージャー」とか、
「タスク スケジューラ」なんかが、
が「scheduler」の例と言えるんですかね?
で、肝心の「job」って?
In computing, a job is a unit of work or unit of execution (that performs said work). A component of a job (as a unit of work) is called a task or a step (if sequential, as in a job stream). As a unit of execution, a job may be concretely identified with a single process, which may in turn have subprocesses (child processes; the process corresponding to the job being the parent process) which perform the tasks or steps that comprise the work of the job; or with a process group; or with an abstract reference to a process or process group, as in Unix job control.
⇧ ということで、
- a nuit of work
- unit of execution(that perfoms said work)
というもので、「a unit of work」のほうは、
- task
- step(if sequential, as in job stream)
のどちらかで知られてるということらしい、知らんかったけど。
ここまでの情報を鑑みるに、
ってことになるんかな?
長々と脱線しましたが、「バッチ処理」というのは、「schedule」された「jobs」ということですと。
ちなみに、「スティーブ・ジョブズ(スティーヴ・ジョブス、英語: Steve Jobs)」は、今回の「jobs」とは全く関係ないので、要注意です。
Javaでバッチ(batch)処理ってどうするのか?
その前に、Javaにおいては、「バッチ(batch)処理」の「標準仕様」が策定されたのが、遅かったらしい。
jBatchは、Java EE 7で初めて導入されたバッチ処理の標準仕様であり、その詳細はJSR-352(Batch Applications for Java Platform)で規定されています。近年のJava EEでは、オープンソース・ソフトウェアの成果を標準技術として積極的に取り込んでいますが、jBatchでも、基本的な仕組みの多くを「Spring Batch」から継承しています。jBatchは、あくまでもバッチ処理の基本部分を標準化したものであり、Spring Batchのほうが機能は豊富ですが、jBatchに準じたバッチ・アプリケーションを作ることで、Java EEに準拠した環境ならどこでも動かせるというメリットがあります。
Java EE 7 jBatchの使い方──『Java EE 7徹底入門』番外編 第3回 - builder by ZDNet Japan
現状、Java EEでバッチ・アプリケーションを作る方法は、jBatchを含めて大きく4つあります。
Java EE 7 jBatchの使い方──『Java EE 7徹底入門』番外編 第3回 - builder by ZDNet Japan
⇧ 何か、Javaにおける「バッチ(batch)処理」の「標準仕様」が「jBatch」ということになったらしいんだけど、経緯としては、
「バッチ処理」という言葉を聞いて、多くの方は「月次の〆処理」や「銀行勘定系システム」、「在庫管理」、「深夜に動く」、「計算量が膨大」、「ジョブ」、「順序制御」、「並列実行」といったキーワードを連想されるだろう。このことから、「多くの業種やシステムに同様の処理が存在し、求められる機能や用途には共通の点が多い」ことがわかる。この「似通った処理機能が、さまざまな業種で個別に開発/利用されてきた」ことが、Java EEでバッチ処理の標準化が求められていた最大の理由だ。
待望のバッチ処理機能がJava EE 7で標準化。「jBatch」で何ができるのか、どう使うのか? - builder by ZDNet Japan
⇧ 開発現場での需要があったってことですかね?
上記のような感じで、各々の開発プロジェクトで自作してたような感じなんですかね?
まぁ、不思議なのが、個別に開発されてたってような「バッチ(Batch)処理」の構築の方法とかって、情報が全く見当たらないんだけど、昔のエンジニアの人の情報発信をGoogleさんはちゃんと上位表示して欲しいんだが...
いや、まぁ、情報発信してくれてるという仮定が矛盾しないとしての話ではありますが...
で、「バッチ(Batch)処理」に関しては、鎖国みたいに閉鎖的な状況が続いていたというのは何となく分かりましたと。
そんなこんなで、個別で開発されてたみたいな「バッチ(Batch)処理」の秘儀みたいな情報とかは、出回っていないので、Javaでレガシーなシステムでなくて「バッチ(batch)処理」をするってなると、「Spring Batch」か「jBatch」が有力候補に挙がってくるってことなんかな?
⇧ 上記サイト様の情報によりますと、「Java EE」と「Spring Framework」は機能を混ぜたら誤動作に繋がることが起こり得るということで、どの「バッチ(Batch)処理」のライブラリを使うかは、開発プロジェクトに依りけりってことですかね。
Spring Batchのアーキテクトって?
「Java EE」が「Oracle」から「Eclipse Foundation」に寄贈されて、「Jakarta EE」とかになって、いろいろ改善されていくのかもしらんけど、今はまだ「Spring Framework」を使っていくのが無難なんではないかということで、「Spring Batch」を使ってみることに。(すみません、後述しますが今回は「Spring Batch」は使いませんでした...)
その前に、「Spring Batch」のアーキテクトってどんな感じなのか?
Spring Batch is designed with extensibility and a diverse group of end users in mind. The figure below shows the layered architecture that supports the extensibility and ease of use for end-user developers.
This layered architecture highlights three major high-level components: Application, Core, and Infrastructure. The application contains all batch jobs and custom code written by developers using Spring Batch.
The Batch Core contains the core runtime classes necessary to launch and control a batch job. It includes implementations for JobLauncher
, Job
, and Step
.
Both Application and Core are built on top of a common infrastructure. This infrastructure contains common readers and writers and services (such as the RetryTemplate
), which are used both by application developers(readers and writers, such as ItemReader
and ItemWriter
) and the core framework itself (retry, which is its own library).
⇧ 大きく分けて、「Batch Core」と「Batch Infrastructure」で機能を分けてるらしいですと。
バージョン1.0.xと旧いのですが、
⇧ 一般的なシンプルな「バッチ(Batch)処理」の構成ってのが、上記のイメージ図のような感じになるってことなんかな?
って思っていたら、「Spring Batch」には処理方式によって、
- Tasklet
- Chunk
の2パターンあるらしく、
Spring Batch Framework offers ‘TaskletStep Oriented’ and ‘Chunk Oriented’ processing style. In this article, Chunk Oriented Processing Model is explained. Also, TaskletStep Oriented Processing in Spring Batch Article is definitely suggested to investigate how to develop TaskletStep Oriented Processing in Spring Batch.
Chunk Oriented Processing Feature has come with Spring Batch v2.0.
⇧ 上記サイト様によりますと、「Chunk」って処理方式については、「Spring Batch」のバージョン 2.0 でお披露目されたらしく、バージョン 1.0のドキュメントの内容は「Tasklet」に関してのみの情報ってことになるっぽい。
何て言うか、経緯とか背景を把握してないと公式のドキュメントも意味不明な部分のほうが多くなってくるから困るよね...
NTT DATAの方が公開してくれてるスライドによると、
⇧「リスタート機能」っていうのが、その語句の意味通りと捉えて良いとすると、何か途中で異常とか起きた場合に、処理が再開できるかどうかってのが一番の違いって感じなんですかね?
なので、
上記の図は、Spring Batch のドメイン言語を構成する重要な概念を強調しています。ジョブには 1 つから多数のステップがあり、各ステップには正確に 1 つの ItemReader
、1 つの ItemProcessor
、1 つの ItemWriter
があります。ジョブを立ち上げる必要があり(JobLauncher
を使用)、現在実行中のプロセスに関するメタデータを(JobRepository
に)格納する必要があります。
⇧「Step」が、
- Tasklet
- Chunk
のどちらかになるにせよ、実際に
- データのインプット
- データの加工
- データのアウトプット
を受け持ってるのは「Step」らしい。
「Spring Batch」のAPIのパッケージ的には、
⇧ みたいな感じになるんかな?(赤枠とか赤字の文言は勝手に加えてます。)
で、「Job」は複数の「Step」を持つことができるというあたりは、この記事の冒頭で「バッチ処理」とは何ぞや?、で調べていて辿り着いた、
の関係に則していると言えますかね。
で、「Step」が実行されるには「Job」が起動してる必要があり、「Job」を起動させるのは「JobLauncher」らしい。
で、肝心の「JobLauncher」は誰が稼働させんのよ?ってのが、
⇧ 何かしら、外部からのアクションが必要らしい。
なんだけど、「Client」についての説明が一切無いというね...
で、ドキュメントを読み進めていくと、「JobLauncher」を稼働させる方法としては、
- コマンドラインからジョブを実行する
- Web コンテナー内からのジョブの実行
の2つのアプローチがあって、「Web コンテナー内からのジョブの実行」ってほうは、
⇧ 何かしら、外部からリクエストが「Controller」に届いて、「Controller」はリクエストを元に、「JobLauncher」を起動させる想定ですと、つまり「オンライン処理」になるんかな?
で、「コマンドラインからジョブを実行する」については、
コマンドラインからジョブを実行する
エンタープライズスケジューラからジョブを実行するユーザーの場合、コマンドラインがプライマリインターフェースです。これは、ほとんどのスケジューラー(NativeJob を使用する場合を除き、Quartz を除く)は、主にシェルスクリプトで開始されるオペレーティングシステムプロセスで直接動作するためです。Perl、Ruby などのシェルスクリプト、あるいは ant や maven などの「ビルドツール」など、Java プロセスを起動する方法は多数あります。ただし、ほとんどの人はシェルスクリプトに精通しているため、この例ではシェルスクリプトに焦点を当てます。
⇧ って説明があり、というか、『ただし、ほとんどの人はシェルスクリプトに精通しているため、この例ではシェルスクリプトに焦点を当てます。』って無言のプレッシャーが半端ない...
というか、「エンタープライズスケジューラ」って何なのよ?
An Enterprise Job Scheduler is a computer application for controlling and automating the unattended programmatic execution of jobs. (Jobs could be in the form of ETL, FTP, Backups, Stored Procedures etc). This is commonly called batch scheduling.
⇧『「ジョブの実行」を操作したり自動化したりするアプリケーション』ってことらしく、一般的には、「batch scheduling」と呼ばれる、とあることから、「バッチ処理」界隈では有名らしい、私は初耳でしたけど...。
なので、よく事前に決めておいた日程なんかに「バッチ処理」が実行されるようにする仕組みは、「コマンドラインからジョブを実行する」になるんかな、それで言うと、「オフライン処理」ってことになるんかな?
「Quartz」と言う名のバッチ処理ライブラリ
職場の先輩に教えていただいたのですが、「Quartz」という「バッチ処理」のライブラリを知りました。
What’s Quartz?
What is the Quartz Job Scheduling Library?
Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for JTA transactions and clustering.
Quartz is freely usable, licensed under the Apache 2.0 license.
⇧ と説明があり、「job scheduling library」というものらしい。
Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system.
⇧ GitHubの説明も「job scheduling library」ですね。
我々は、この記事の冒頭で、心許ない綱渡り感のある仮定と推測によって
ということを突き詰めていたので、
という仮定が矛盾しないという前提で話を進めていきます。
で、「『バッチ処理』に関するライブラリ」の1種であると思われる「Quartz」ですが、「エンタープライズスケジューラ」の1種になるんかな?
まぁ、「エンタープライズスケジューラ」の1種であると仮定して矛盾しない前提で話を進めると、「エンタープライズスケジューラ」は一般的に「batch scheduling」すると呼ばれると言っていたので、「バッチ処理」が実装できるライブラリってことがほぼほぼ言えるんじゃないでしょうか?
いやはや、相変わらず、用語の解釈に時間を要しますな...もっと他のことに時間を使いたいところなんですけどね...
というわけで、使ってみる
物は試し、というわけで、使ってみる。
⇧ 上記サイト様を参考に実施してみました。
「定期実行」みたいなことは、頑張れば、Java SEの標準APIの「ScheduledExecutorService」ってので実装できるらしいのですが、「Quartz」を使えば、簡単に...いかないみたいですね...
どうも、「30日」とか「60日」とか「90日」とか、長いスパンでの「定期実行」に関しては、情報が少ないみたいですね、実際の現場で絶対に用途があるはず技術って、どうにも情報が出回らない感がありますね。
まぁ、stackoverflowを見て頑張りますか。
と思ったら、「Quartz」の「Trigger」についてのチュートリアルで説明されてる模様。
1つ古いバージョンのチュートリアルで、どんな値を設定できるかとかの説明があるっぽいです。
⇧ 上記のように、「Trigger」で、どのタイミングで「バッチ処理」が実行されるようにするかを設定できる模様。
というわけで、Eclipseを起動し、「ファイル(F)」>「新規(N)」>「Gradle プロジェクト」を選択。
「プロジェクト名」を適当に入力し、「次へ(N)>」を押下。
特に何も変更せずデフォルトの状態で、「次へ(N)>」を押下しました。
「完了(F)」を押下。
「Gradle プロジェクト」が作成されたら、「build.gradle」に「Quartz」のライブラリを依存関係として追加していきます。
で、その前に「Eclipse」で作成された「Gradle プロジェクト」で利用されてる「Gradle」のバージョンが旧かったので、「Gradle プロジェクト」の「Gradle」のバージョンを上げました。
「Gradle Dsitributions Snapshots」と、
「Gradle Distirbutions」で、
何が違うか分からんけども、今回は「Gradle Dsitribution Snapshots」を利用することに。
「Gradle プロジェクト」内の「gradle」フォルダにある「gradle-wrapper.properties」を開いて、
「distributionUrl」を編集。「Gradle Distirbution Snapshots」のリンクをコピーしてきて、以下のように「https」と「:」の間に「\」を追記して設定で。
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-bin.zip distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-7.4-20211104232430+0000-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists
⇧ 今回はバージョンを「7.4」にしてみました。
そしたらば、「Gradle」>「Gradle プロジェクトのリフレッシュ」しときます。
脱線しましたが、「build.gradle」に追加する「依存関係」は、
⇧ 上記のページの「Downloads」のページの情報より、「groupId」が「org.quartz-scheduler」のライブラリを追加すれば良いと思ったのですが、「Spring Boot」を使う場合、「spring-boot-starter-quartz」って依存関係があるようなので、「MVN Repository」のサイトで、諸々の依存関係を探して、その他にも、「JDBCドライバ(PostgresSQL用)」、「Spring Boot Starter Data JPA」、「Jackson」の依存関係とか何やかんやを追加して、「build.gradle」は以下のようになりました。
■/QuartzProject/build.gradle
/* * This file was generated by the Gradle 'init' task. * * This generated file contains a sample Java Library project to get you started. * For more details take a look at the Java Libraries chapter in the Gradle * User Manual available at https://docs.gradle.org/5.6.1/userguide/java_library_plugin.html */ plugins { // Apply the java-library plugin to add support for Java Library id 'java-library' id 'org.springframework.boot' version '2.5.6' id 'io.spring.dependency-management' version '1.0.11.RELEASE' } repositories { // Use jcenter for resolving dependencies. // You can declare any Maven/Ivy/file repository here. jcenter() } dependencies { // This dependency is exported to consumers, that is to say found on their compile classpath. api 'org.apache.commons:commons-math3:3.6.1' // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:28.0-jre' // Use JUnit test framework testImplementation 'junit:junit:4.12' // https://mvnrepository.com/artifact/org.quartz-scheduler/quartz //implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2' // https://mvnrepository.com/artifact/org.postgresql/postgresql implementation group: 'org.postgresql', name: 'postgresql', version: '42.3.1' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter implementation group: 'org.springframework.boot', name: 'spring-boot-starter', version: '2.5.6' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz implementation group: 'org.springframework.boot', name: 'spring-boot-starter-quartz', version: '2.5.6' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.5.6' // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.0' // https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.4.0-b180830.0359' // https://mvnrepository.com/artifact/com.mchange/c3p0 implementation group: 'com.mchange', name: 'c3p0', version: '0.9.5.5' }
⇧ ちょっと、「Gradle プロジェクト」作成時の「Gradle」が旧かったせいなのか、デフォルトで「build.gradle」に記載されてる「依存関係」のバージョンが旧いような気がしなくもないですが...
で、「PostgreSQL」の「テーブル」なんかについては、
⇧ 前回の記事で作成したものを使っていこうかと。「テーブル」としては、5つ作ってる感じです。
⇧ 上記の「テーブル」については、何気なしに、
⇧ 上図を参考に作成したのですが、「User」の「user_email_token_expires」ってのが、「user_email_token」ってものの「有効期限」っぽく、今を時めく「OAuth 2.0」の仕様を見ると、
RFC 6749 OAuth 2.0 October 2012 4.2.2. Access Token Response If the resource owner grants the access request, the authorization server issues an access token and delivers it to the client by adding the following parameters to the fragment component of the redirection URI using the "application/x-www-form-urlencoded" format, per Appendix B: access_token REQUIRED. The access token issued by the authorization server. token_type REQUIRED. The type of the token issued as described in Section 7.1. Value is case insensitive. expires_in RECOMMENDED. The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value.
⇧ 「expires_in」って項目があって、一般的に「token」の「有効期限」ってのは「second(秒)」で保持するのがデファクトスタンダードらしいですと。
となると、管理する側としては、「token」が作成された「日時」を保持してないと、「有効期限」を過ぎたのかどうかを判定できませんと。
で、「テーブル」を作成するのに参考にした「データモデリング」はと言うと、
⇧ 作成した日時、どれなんやろ?ってな感じで、こういう時に役立つのがドキュメント、つまり「設計書」とかだとは思うのだけど、そもそも「設計書」なんて無いよ、というような開発現場もあり、推測するしかないがために個々人によって解釈が異なり、認識齟齬の雨霰が今日も各地で起こるのであった...
まぁ、話を元に戻すと、
項目 | データ型 | 格納サイズ (バイト) |
備考 |
---|---|---|---|
user_email_token_created | timestamp | 8 | user_email_tokenが作成された日時 YYYY/MM/DD hh:mm:ss |
みたいな「項目」はあったほうが良い気がするんですが、「User」を作った人の意図が分からんのが困ったところですね...
なので、実際の開発現場では、とりあえず、「自分はこういう風に考えたけど合ってます?」みたいな感じで「認識合わせ」は超重要ってことですね。
有識者の人とかなら、「たぶん、こういうことを聞きたいんだろうな」って感じで情報を正しい方向へ導いてくれることが多いので、自分なりの「ここがよく分からない」ってのをアウトプットしていくのは大事!
もし、誰も分から~んってなったら、その時にチームのみんなで考えれば良いですし。
度々、話が脱線してすみません。
そんなわけで、「user」テーブルに「user_email_token_created」って「カラム」を追加することにします。
本当は、追加する「カラム」の位置を指定したかったのですが、
postgresqlの初心者の多くからよく、テーブル内の列の位置を変更することはサポートされているかどうか聞かれます。 今のところサポートされていません。 列の位置を変更したいのであれば、テーブルを再作成するか、新しい列を追加してデータを移動するかのいずれかを行う必要があります。 列の位置の順序の変更を許すという考えをpostgresql開発者は対象としていません。
- This page was last edited on 19 December 2009, at 07:12.
⇧ いまのところ、と言っていたのが「2009年12月19日」だとすると、いまが「2021年11月3日」だから、大体12年ぐらい経過してるっぽいのだが、
Rails+Postgresqlの環境です。
テーブルの任意の場所にカラムを追加したいのですが、Postgresqlの場合、afterが有効にならず、テーブルの最後のカラムの後にしかカラムが追加されないのは周知の事実だと思います。
⇧ って「問いかけ」に対して、「回答」で特に指摘が無いところを見ると、「PostgreSQL」では「カラム」の追加は「テーブル」の「カラム」の末尾に追加されていく仕様が変わることは無さそうですね。
話を戻して、インストールしていた「PostgreSQL」に接続して、「テーブル」を作成していた「postgres」データベースに接続します。
前回インストールしてたのが「PostgreSQL 14」になります。
では、接続。
「user」テーブルに「user_email_token_created」って「カラム」を追加で。(SQL文をいろいろミスって申し訳ない。)
追加できました。
データも追加しておくことにします。
ただ、「PostgreSQL」で「テーブル」に「パスワード」とか保存する際に、「パスワード」を「平文」で保存するってことは宜しくないということで、「ハッシュ化」とかしたほうが良いと思われるのですが、
pgcrypto
モジュールはPostgreSQL用の暗号関数を提供します。
このモジュールは「trusted」と見なされます。つまり、現在のデータベースに対してCREATE
権限を持つ非スーパーユーザがインストールできます。
⇧ という「暗号関数」が「PostgreSQL」には用意されてるらしいのですが、
この付録と次の付録にはPostgreSQL配布物の
contrib
ディレクトリにあるモジュールに関する情報があります。 ここには、移植用のツール、解析ユーティリティ、限定した利用者を対象にしていること、または、主ソースツリーに含めるには実験的すぎることが主な理由でPostgreSQLのコアシステムにはないプラグイン機能が含まれます。 これはその有用性を妨げるものではありません。
多くのモジュールは新しいユーザ定義関数、演算子、型を提供します。 こうしたモジュールの1つを使用できるようにするためには、コードをインストールした後に、新しいSQLオブジェクトをデータベースサーバに登録する必要があります。 これはCREATE EXTENSIONコマンドを実行することで行われます。
⇧ とあって、「contrib」ディレクトリに格納されてる「モジュール」であれば、「CREATE EXTENTION [モジュール]」って感じのSQL文を実行すれば「モジュール」が利用できるようになるらしい。
で、Windows環境の「PostgreSQL」では、「contrib」ディレクトリなんてものは存在せず、「lib」ディレクトリが該当するらしい...
公式のドキュメントが全く役に立たん...
で、「モジュール」が有効になってるかどうかは、以下のSQL文で確認できるらしい。
select * from pg_available_extensions;
name | default_version | installed_version | comment ---------------------+-----------------+-------------------+------------------------------------------------------------------------ adminpack | 2.1 | 2.1 | administrative functions for PostgreSQL amcheck | 1.3 | | functions for verifying relation integrity autoinc | 1.0 | | functions for autoincrementing fields bloom | 1.0 | | bloom access method - signature file based index bool_plperl | 1.0 | | transform between bool and plperl bool_plperlu | 1.0 | | transform between bool and plperlu btree_gin | 1.3 | | support for indexing common datatypes in GIN btree_gist | 1.6 | | support for indexing common datatypes in GiST citext | 1.6 | | data type for case-insensitive character strings cube | 1.5 | | data type for multidimensional cubes dblink | 1.2 | | connect to other PostgreSQL databases from within a database dict_int | 1.0 | | text search dictionary template for integers dict_xsyn | 1.0 | | text search dictionary template for extended synonym processing dummy_index_am | 1.0 | | dummy_index_am - index access method template earthdistance | 1.1 | | calculate great-circle distances on the surface of the Earth file_fdw | 1.0 | | foreign-data wrapper for flat file access fuzzystrmatch | 1.1 | | determine similarities and distance between strings hstore | 1.8 | | data type for storing sets of (key, value) pairs hstore_plperl | 1.0 | | transform between hstore and plperl hstore_plperlu | 1.0 | | transform between hstore and plperlu hstore_plpython2u | 1.0 | | transform between hstore and plpython2u hstore_plpython3u | 1.0 | | transform between hstore and plpython3u hstore_plpythonu | 1.0 | | transform between hstore and plpythonu insert_username | 1.0 | | functions for tracking who changed a table intagg | 1.1 | | integer aggregator and enumerator (obsolete) intarray | 1.5 | | functions, operators, and index support for 1-D arrays of integers isn | 1.2 | | data types for international product numbering standards jsonb_plperl | 1.0 | | transform between jsonb and plperl jsonb_plperlu | 1.0 | | transform between jsonb and plperlu jsonb_plpython2u | 1.0 | | transform between jsonb and plpython2u jsonb_plpython3u | 1.0 | | transform between jsonb and plpython3u jsonb_plpythonu | 1.0 | | transform between jsonb and plpythonu lo | 1.1 | | Large Object maintenance ltree | 1.2 | | data type for hierarchical tree-like structures ltree_plpython2u | 1.0 | | transform between ltree and plpython2u ltree_plpython3u | 1.0 | | transform between ltree and plpython3u ltree_plpythonu | 1.0 | | transform between ltree and plpythonu moddatetime | 1.0 | | functions for tracking last modification time old_snapshot | 1.0 | | utilities in support of old_snapshot_threshold pageinspect | 1.9 | | inspect the contents of database pages at a low level pgcrypto | 1.3 | | cryptographic functions pgrowlocks | 1.2 | | show row-level locking information pgstattuple | 1.5 | | show tuple-level statistics pg_buffercache | 1.3 | | examine the shared buffer cache pg_freespacemap | 1.2 | | examine the free space map (FSM) pg_prewarm | 1.2 | | prewarm relation data pg_stat_statements | 1.9 | | track planning and execution statistics of all SQL statements executed pg_surgery | 1.0 | | extension to perform surgery on a damaged relation pg_trgm | 1.6 | | text similarity measurement and index searching based on trigrams pg_visibility | 1.2 | | examine the visibility map (VM) and page-level visibility info pldbgapi | 1.1 | | server-side support for debugging PL/pgSQL functions plperl | 1.0 | | PL/Perl procedural language plperlu | 1.0 | | PL/PerlU untrusted procedural language plpgsql | 1.0 | 1.0 | PL/pgSQL procedural language plpython2u | 1.0 | | PL/Python2U untrusted procedural language plpython3u | 1.0 | | PL/Python3U untrusted procedural language plpythonu | 1.0 | | PL/PythonU untrusted procedural language plsample | 1.0 | | PL/Sample pltcl | 1.0 | | PL/Tcl procedural language pltclu | 1.0 | | PL/TclU untrusted procedural language postgres_fdw | 1.1 | | foreign-data wrapper for remote PostgreSQL servers refint | 1.0 | | functions for implementing referential integrity (obsolete) seg | 1.4 | | data type for representing line segments or floating-point intervals spgist_name_ops | 1.0 | | Test opclass for SP-GiST sslinfo | 1.2 | | information about SSL certificates system_stats | 1.0 | | EnterpriseDB system statistics for PostgreSQL tablefunc | 1.0 | | functions that manipulate whole tables, including crosstab tcn | 1.0 | | Triggered change notifications test_bloomfilter | 1.0 | | Test code for Bloom filter library test_ext1 | 1.0 | | Test extension 1 test_ext2 | 1.0 | | Test extension 2 test_ext3 | 1.0 | | Test extension 3 test_ext4 | 1.0 | | Test extension 4 test_ext5 | 1.0 | | Test extension 5 test_ext6 | 1.0 | | test_ext6 test_ext7 | 1.0 | | Test extension 7 test_ext8 | 1.0 | | Test extension 8 test_ext_cyclic1 | 1.0 | | Test extension cyclic 1 test_ext_cyclic2 | 1.0 | | Test extension cyclic 2 test_ext_evttrig | 1.0 | | Test extension - event trigger test_ginpostinglist | 1.0 | | Test code for ginpostinglist.c test_integerset | 1.0 | | Test code for integerset test_pg_dump | 1.0 | | Test pg_dump with an extension test_predtest | 1.0 | | Test code for optimizer/util/predtest.c test_rbtree | 1.0 | | Test code for red-black tree library test_regex | 1.0 | | Test code for backend/regex/ tsm_system_rows | 1.0 | | TABLESAMPLE method which accepts number of rows as a limit tsm_system_time | 1.0 | | TABLESAMPLE method which accepts time in milliseconds as a limit unaccent | 1.1 | | text search dictionary that removes accents uuid-ossp | 1.1 | | generate universally unique identifiers (UUIDs) xml2 | 1.1 | | XPath querying and XSLT (91 行)
⇧「installed_version」に「バージョン」が入って無いものは有効になってないってことなんかな?
とりあえず、「CREATE EXTENTION」コマンドを試してみる。
CREATE EXTENTION pgcrypt;
name | default_version | installed_version | comment ---------------------+-----------------+-------------------+------------------------------------------------------------------------ adminpack | 2.1 | 2.1 | administrative functions for PostgreSQL amcheck | 1.3 | | functions for verifying relation integrity autoinc | 1.0 | | functions for autoincrementing fields bloom | 1.0 | | bloom access method - signature file based index bool_plperl | 1.0 | | transform between bool and plperl bool_plperlu | 1.0 | | transform between bool and plperlu btree_gin | 1.3 | | support for indexing common datatypes in GIN btree_gist | 1.6 | | support for indexing common datatypes in GiST citext | 1.6 | | data type for case-insensitive character strings cube | 1.5 | | data type for multidimensional cubes dblink | 1.2 | | connect to other PostgreSQL databases from within a database dict_int | 1.0 | | text search dictionary template for integers dict_xsyn | 1.0 | | text search dictionary template for extended synonym processing dummy_index_am | 1.0 | | dummy_index_am - index access method template earthdistance | 1.1 | | calculate great-circle distances on the surface of the Earth file_fdw | 1.0 | | foreign-data wrapper for flat file access fuzzystrmatch | 1.1 | | determine similarities and distance between strings hstore | 1.8 | | data type for storing sets of (key, value) pairs hstore_plperl | 1.0 | | transform between hstore and plperl hstore_plperlu | 1.0 | | transform between hstore and plperlu hstore_plpython2u | 1.0 | | transform between hstore and plpython2u hstore_plpython3u | 1.0 | | transform between hstore and plpython3u hstore_plpythonu | 1.0 | | transform between hstore and plpythonu insert_username | 1.0 | | functions for tracking who changed a table intagg | 1.1 | | integer aggregator and enumerator (obsolete) intarray | 1.5 | | functions, operators, and index support for 1-D arrays of integers isn | 1.2 | | data types for international product numbering standards jsonb_plperl | 1.0 | | transform between jsonb and plperl jsonb_plperlu | 1.0 | | transform between jsonb and plperlu jsonb_plpython2u | 1.0 | | transform between jsonb and plpython2u jsonb_plpython3u | 1.0 | | transform between jsonb and plpython3u jsonb_plpythonu | 1.0 | | transform between jsonb and plpythonu lo | 1.1 | | Large Object maintenance ltree | 1.2 | | data type for hierarchical tree-like structures ltree_plpython2u | 1.0 | | transform between ltree and plpython2u ltree_plpython3u | 1.0 | | transform between ltree and plpython3u ltree_plpythonu | 1.0 | | transform between ltree and plpythonu moddatetime | 1.0 | | functions for tracking last modification time old_snapshot | 1.0 | | utilities in support of old_snapshot_threshold pageinspect | 1.9 | | inspect the contents of database pages at a low level pgcrypto | 1.3 | 1.3 | cryptographic functions pgrowlocks | 1.2 | | show row-level locking information pgstattuple | 1.5 | | show tuple-level statistics pg_buffercache | 1.3 | | examine the shared buffer cache pg_freespacemap | 1.2 | | examine the free space map (FSM) pg_prewarm | 1.2 | | prewarm relation data pg_stat_statements | 1.9 | | track planning and execution statistics of all SQL statements executed pg_surgery | 1.0 | | extension to perform surgery on a damaged relation pg_trgm | 1.6 | | text similarity measurement and index searching based on trigrams pg_visibility | 1.2 | | examine the visibility map (VM) and page-level visibility info pldbgapi | 1.1 | | server-side support for debugging PL/pgSQL functions plperl | 1.0 | | PL/Perl procedural language plperlu | 1.0 | | PL/PerlU untrusted procedural language plpgsql | 1.0 | 1.0 | PL/pgSQL procedural language plpython2u | 1.0 | | PL/Python2U untrusted procedural language plpython3u | 1.0 | | PL/Python3U untrusted procedural language plpythonu | 1.0 | | PL/PythonU untrusted procedural language plsample | 1.0 | | PL/Sample pltcl | 1.0 | | PL/Tcl procedural language pltclu | 1.0 | | PL/TclU untrusted procedural language postgres_fdw | 1.1 | | foreign-data wrapper for remote PostgreSQL servers refint | 1.0 | | functions for implementing referential integrity (obsolete) seg | 1.4 | | data type for representing line segments or floating-point intervals spgist_name_ops | 1.0 | | Test opclass for SP-GiST sslinfo | 1.2 | | information about SSL certificates system_stats | 1.0 | | EnterpriseDB system statistics for PostgreSQL tablefunc | 1.0 | | functions that manipulate whole tables, including crosstab tcn | 1.0 | | Triggered change notifications test_bloomfilter | 1.0 | | Test code for Bloom filter library test_ext1 | 1.0 | | Test extension 1 test_ext2 | 1.0 | | Test extension 2 test_ext3 | 1.0 | | Test extension 3 test_ext4 | 1.0 | | Test extension 4 test_ext5 | 1.0 | | Test extension 5 test_ext6 | 1.0 | | test_ext6 test_ext7 | 1.0 | | Test extension 7 test_ext8 | 1.0 | | Test extension 8 test_ext_cyclic1 | 1.0 | | Test extension cyclic 1 test_ext_cyclic2 | 1.0 | | Test extension cyclic 2 test_ext_evttrig | 1.0 | | Test extension - event trigger test_ginpostinglist | 1.0 | | Test code for ginpostinglist.c test_integerset | 1.0 | | Test code for integerset test_pg_dump | 1.0 | | Test pg_dump with an extension test_predtest | 1.0 | | Test code for optimizer/util/predtest.c test_rbtree | 1.0 | | Test code for red-black tree library test_regex | 1.0 | | Test code for backend/regex/ tsm_system_rows | 1.0 | | TABLESAMPLE method which accepts number of rows as a limit tsm_system_time | 1.0 | | TABLESAMPLE method which accepts time in milliseconds as a limit unaccent | 1.1 | | text search dictionary that removes accents uuid-ossp | 1.1 | | generate universally unique identifiers (UUIDs) xml2 | 1.1 | | XPath querying and XSLT (91 行)
⇧ すると、まぁ、何と言うことでしょう!
「pgcrypt」モジュールの「installed_version」に「バージョン」が追加されたではありませんか!
ただ、
⇧「関数」によっては、「bytea」じゃない「データ型」が返されるらしい...
とりあえず、「digest」って関数を使ってみた。
INSERT INTO public.user (user_id, user_name, user_real_name, user_password, user_newpassword, user_email, user_options, user_touched, user_token, user_email_authenticated, user_email_token, user_email_token_expires, user_registration, user_email_token_created) VALUES (1, 'sato', '佐藤', digest('password0000001', 'sha256'),'' , 'sato@gmail.com', '', '', '6WJ+sNg3R2JZzQP3F7d5B0ke6AQBNqre', '', 'EovQAEnJubjXxy5ZBp+qoaHm9H1nOdnN', '7200000', '', '2021-07-01 09:00:00'), (2, 'suzuki', '鈴木', digest('password0000002', 'sha256'),'' , 'suzuki@gmail.com', '', '', 'oLu44GMjdNAa7js8xLS4yJGrRtsp89ad', '', '1IFrgAsQtkrM+2F0oGSUO7ReXkxvJVmU', '7200000', '', '2021-04-01 09:00:00'), (3, 'takahashi', '高橋', digest('password0000003', 'sha256'),'' , 'takahashi@gmail.com', '', '', 'IffbaNHYoQAsNm77bc7rMYygua5O62ch', '', '+HPfhm96+KLXm2Zz9LjwdgUHybCpdH1E', '7200000', '', '2021-10-01 09:00:00'), (4, 'tanaka', '田中', digest('password0000004', 'sha256'),'' , 'tanaka@gmail.com', '', '', '9NlNXNr2Kqi0b8k4ZXnvuyU6nHCTlGDn', '', 'e3lbYMHLxcJZcisNxBwtkYNuw19fEsot', '7200000', '', '2021-11-01 09:00:00');
とりあえず、データは入った模様。(データはめっちゃ適当です...)
あと、気を付けたいのが、「PostgreSQL」以外の「データベース」でも同じだと思うけど「charcter(char)」型の「カラム」は桁数固定なんで、桁数に満たない場合は、「空白スペース」で埋められるので、「空白スペース」が不要の場合はプログラム側で「空白スペース」を「trim」したりの対応が必要になりますかね。
で、何か「Quartz」で「データベース」を利用したい場合は、
⇧「quartz」用のテーブルを作る必要があるらしい。今回は、「PostgreSQL」を使っているので、「tables_postgres.sql」を実行すれば、良いらしい、たぶん...。
curl -O https://raw.githubusercontent.com/quartz-scheduler/quartz/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql
で、ダウンロードしたsqlファイルを「PostgreSQL」にログインしてる状態で実行。
\i 'C:\\Eclipse_2019-09\\pleiades-2019-09-java-win-64bit-jre_20191007\\pleiades\\workspace\\work_00\\QuartzProject\\src\\main\\resources\\sql\\tables_postgres.sql'
⇧ 「quartz」用の「テーブル」ができたっぽい。
ようやっと、Javaのほうに取りかかります。
民法 第一編 第六章 期間の計算(第138条―第143条)に期間計算についての記述があります。
この条文を忠実に守るのも1つの仕様、luckyclockさんオリジナルの期間計算法を定めるのも1つの仕様です。
仕様があいまいだと、実装や検査でつらい思いをしますので、まずは仕様を固めることをお勧めします。
Excelの期間計算関数DATEDIFに深刻なバグがあることをご存じでしょうか?
このバグ回避のための、かつ民法に沿った方法がネット上にいろいろ公開されていますので、それを参考にするのも1つの手です。
⇧ というように、「期間計算」はいろいろ厄介な感じですかね...
さらに、
対策としては、time_t
型を符号付き64ビット整数型(一般にはlong long int
型)にするという方法がある。符号付き64ビット整数型の場合、上限は9,223,372,036,854,775,807 (263 − 1) である。これを年数に変換するとおよそ西暦3000億年まで使用できるので、事実上問題が発生することはない。
⇧ ってな問題もあるらしい。
Javaの「プリミティブ型」の1つである「long型」はと言うと、
Field Summary
static final long
long
can have, 263-1.https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html
⇧「Long」クラスの「MAX_VALUE」ってフィールドの説明で、「long型」が、「」まで表現できるとあるので、Wikipediaの「2038年問題」の対応としては問題ないことになるみたいね。
ただ、
⇧ 上記サイト様で「Quartz」については、「2038年問題」に対応してないんじゃないか疑惑があるっぽい、誰も回答しておらんので真偽は不明だけども。
ちなみに、
太陽でおきている核融合は、水素1gから7000億kWhものエネルギーを取り出す非常に効率のよい反応です。しかし燃料に限りがある以上、この反応にもいつかは終わりがやってきます。理論計算によると、太陽は約100億年の寿命があります。太陽系が生まれたのは46億年前ですから、太陽はあと50億年は今と同じように輝き続けることができます。
⇧ 上記サイト様によりますと「太陽」の「寿命」があと「50億年」ぐらいって想定らしいので、「3000億年」を賄える「long型」があれば十分ということになりますかね。
なんか、同じ人類で対立してる暇があったら、未来を生きることになる後進の人たちのために研究とかに時間を費やしていった方が良い気がするんですけどね...
それにしても、「50億年」ぐらいで「太陽」が無くなるかもしれないってことは、「太陽系」以外で人類が生きていく場所を探す宇宙時代が来るってことですかね。
そう考えると、「宇宙」に対する研究に力を入れていくのも人類のためになるということですかね。
話が脱線しましたが、Eclipseの「パッケージ・エクスプローラー」上のファイルは以下のような感じに。
⇧ 何だかんだで、ファイル数がかなり多くなってしまった...
しかも、ちゃんと処理について考えてないので、「アンチパターン」っぽくなってしまっています、SQLで、全件取得じゃなくて、候補を絞れば良かったのもあるし...
⇧ 上記サイト様で仰っているように、複数のリストを同時にループさせるのは、宜しくなさそうなのですが、Pythonとかだと、
⇧ 標準で用意されてる「zip」って関数で、同時に複数のリストをループさせる仕組みはある模様。
そして、そもそも、外部システムから連携される想定の「JSON(JavaScript Object Notation)」の「プロパティ」がすべて「配列」になってる作りが宜しくないですね(実際、そんな構造になってる「JSON(JavaScript Object Notation)」は無いと思う...自分で勝手に作っておいてなんだけど...
あるとしたら、「オブジェクト」の「配列」みたいな作りになる感じかしらね...
脱線しましたが、ソースコードは以下のような感じになりました。
■/QuartzProject/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
■/QuartzProject/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
■/QuartzProject/src/main/java/QuartzProject/config/QuartzConfig.java
package QuartzProject.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(); } }
■/QuartzProject/src/main/java/QuartzProject/config/AutowiringSpringBeanJobFactory.java
package QuartzProject.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 applicationContext) { beanFactory = applicationContext.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } }
■/QuartzProject/src/main/java/QuartzProject/entity/UserPrimaryKey.java
package QuartzProject.entity; import java.io.Serializable; import javax.persistence.Column; /** * PostgreSQLのuserテーブルの主キー(複合主キー)用クラス * @author Toshinobu * */ public class UserPrimaryKey implements Serializable { /** * 生成シリアル・バージョンID */ private static final long serialVersionUID = -6337698001336117167L; @Column(name="user_id") private int userId; @Column(name="user_name") private String userName; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
■/QuartzProject/src/main/java/QuartzProject/entity/UserEntity.java
package QuartzProject.entity; import java.io.Serializable; import java.sql.Timestamp; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; /** * PostgreSQLのuserテーブルに紐づくEntityクラス * @author Toshinobu * */ @Entity @Table(name="user", schema = "public") @IdClass(value=UserPrimaryKey.class) public class UserEntity implements Serializable { /** * */ private static final long serialVersionUID = 1548577324049557983L; @Id @Column(name="user_id") private int userId; @Id @Column(name="user_name") private String userName; @Column(name="user_real_name") private String userRealName; @Column(name="user_password") private byte[] userPassword; @Column(name="user_newpassword") private byte[] userNewPassword; @Column(name="user_email") private String userEmail; @Column(name="user_options") private byte[] userOptions; @Column(name="user_touched", columnDefinition = "bpchar") private String userTouched; @Column(name="user_token", columnDefinition = "bpchar") private String userToken; @Column(name="user_email_authenticated", columnDefinition = "bpchar") private String userEmailAuthenticated; @Column(name="user_email_token", columnDefinition = "bpchar") private String userEmailToken; @Column(name="user_email_token_expires", columnDefinition = "bpchar") private String userEmailTokenExpires; @Column(name="user_registration", columnDefinition = "bpchar") private String userRegistration; @Column(name="user_email_token_created") private Timestamp userEmailTokenCreated; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserRealName() { return userRealName; } public void setUserRealName(String userRealName) { this.userRealName = userRealName; } public byte[] getUserPassword() { return userPassword; } public void setUserPassword(byte[] userPassword) { this.userPassword = userPassword; } public byte[] getUserNewPassword() { return userNewPassword; } public void setUserNewPassword(byte[] userNewPassword) { this.userNewPassword = userNewPassword; } public String getUserEmail() { return userEmail; } public void setUserEmail(String userEmail) { this.userEmail = userEmail; } public byte[] getUserOptions() { return userOptions; } public void setUserOptions(byte[] userOptions) { this.userOptions = userOptions; } public String getUserTouched() { return userTouched; } public void setUserTouched(String userTouched) { this.userTouched = userTouched; } public String getUserToken() { return userToken; } public void setUserToken(String userToken) { this.userToken = userToken; } public String getUserEmailAuthenticated() { return userEmailAuthenticated; } public void setUserEmailAuthenticated(String userEmailAuthenticated) { this.userEmailAuthenticated = userEmailAuthenticated; } public String getUserEmailToken() { return userEmailToken; } public void setUserEmailToken(String userEmailToken) { this.userEmailToken = userEmailToken; } public String getUserEmailTokenExpires() { return userEmailTokenExpires; } public void setUserEmailTokenExpires(String userTokenExpires) { this.userEmailTokenExpires = userTokenExpires; } public String getUserRegistration() { return userRegistration; } public void setUserRegistration(String userRegistration) { this.userRegistration = userRegistration; } public Timestamp getUserEmailTokenCreated() { return userEmailTokenCreated; } public void setUserEmailTokenCreated(Timestamp userEmailTokenCreated) { this.userEmailTokenCreated = userEmailTokenCreated; } }
■/QuartzProject/src/main/java/QuartzProject/http/response/dto/UserEmailTokenResponseDto.java
package QuartzProject.http.response.dto; import java.util.HashMap; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; /** * JSONに紐づくDTOクラス * @author Toshinobu * */ public class UserEmailTokenResponseDto { @JsonProperty("userId") public List<String> userId; @JsonProperty("userName") public List<String> userName; @JsonProperty("userEmailToken") public List<String> userEmailToken; @JsonProperty("userEmailTokenExpires") public List<String> userEmailTokenExpiers; @JsonProperty("userEmailTokenCreated") public List<String> userEmailTokenCreated; @JsonIgnore private Map<String, Object> additionalProperties = new HashMap<String, Object>(); @JsonAnyGetter public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; } @JsonAnySetter public void setAdditionalProperty(String name, Object value) { this.additionalProperties.put(name, value); } }
■/QuartzProject/src/main/java/QuartzProject/component/http/UserHttpComponent.java
package QuartzProject.component.http; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import QuartzProject.http.response.dto.UserEmailTokenResponseDto; @Component public class UserHttpComponent { /** * JSONからJavaオブジェクトに変換 * @param json * @return * @throws JsonMappingException * @throws JsonProcessingException */ public UserEmailTokenResponseDto convertJavaFromJson(String json) throws JsonMappingException, JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(json, UserEmailTokenResponseDto.class); } }
■/QuartzProject/src/main/java/QuartzProject/repository/UserRepository.java
package QuartzProject.repository; import org.springframework.data.jpa.repository.JpaRepository; import QuartzProject.entity.UserEntity; import QuartzProject.entity.UserPrimaryKey; public interface UserRepository extends JpaRepository<UserEntity, UserPrimaryKey> { }
■/QuartzProject/src/main/java/QuartzProject/service/UserServiceImpl.java
package QuartzProject.service; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; import QuartzProject.component.http.UserHttpComponent; import QuartzProject.entity.UserEntity; import QuartzProject.http.response.dto.UserEmailTokenResponseDto; import QuartzProject.repository.UserRepository; @Service public class UserServiceImpl { @Autowired private UserRepository userRepository; @Autowired private UserHttpComponent userHttpComponent; /** * userテーブル全件取得 * @return */ public List<UserEntity> searchAll() { return userRepository.findAll(); } /** * user_mail_tokenの更新 * @param userEntityList */ @Transactional public void updateUserMailToken(List<UserEntity> userEntityList) { final long ONE_DAY_MINUTES = 24 * 60 * 60; // 1日(秒) final long UPDATE_DAY_MINUTES = 60 * ONE_DAY_MINUTES; // 60日(秒) Timestamp todayTimeStamp = new Timestamp(System.currentTimeMillis()); // 現在日時 // 更新対象の絞り込み List<UserEntity> updateUserEmailCreatedList = userEntityList .stream() .filter(userEntity -> Objects.nonNull(userEntity) && Objects.nonNull(userEntity.getUserEmailTokenExpires()) && Objects.nonNull(userEntity.getUserEmailTokenCreated())) // 絶対値(「現在日時(秒)」 - 「作成日時(秒)」) >= 「60日(秒)」のものに絞り込み .filter(userEntity -> Math.abs( TimeUnit.MILLISECONDS.toSeconds(todayTimeStamp.getTime()) - TimeUnit.MILLISECONDS .toSeconds(userEntity.getUserEmailTokenCreated().getTime())) >= UPDATE_DAY_MINUTES) .collect(Collectors.toList()); // TODO:外部システムに対してtokenの再発行を行う // 外部システムから取得した体で「token」「tokenの有効期限」「tokenの作成日時」で「user」テーブルを更新する String json = "{" + " \"userId\": [\"1\", \"2\"], " + " \"userName\": [\"sato\", \"suzuki\"], " + " \"userEmailToken\": [\"UovQAEnJubjrxy5ZBp+qoaHm9H1nOdnN\", \"8IFrgAsQtkrM+2k0oGSUO7ReXkxvJVmU\"], "+ " \"userEmailTokenExpires\": [\"720000\", \"720000\"], " + " \"userEmailTokenCreated\": [\"2021-11-06 00:00:00\", \"2021-11-06 00:00:00\"]" + " }" + "}"; UserEmailTokenResponseDto userEmailTokenResponseDto = null; try { userEmailTokenResponseDto = userHttpComponent.convertJavaFromJson(json); } catch (JsonProcessingException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } // 更新対象のエンティティを作成し、Listに格納 List<UserEntity> updateUserEntityList = new ArrayList<>(); int indexUpdateUserList = 0, indexUserId = 0, indexUserName = 0, indexUserEmailToken = 0, indexUserEmailTokenExpire = 0, indexUserEmailTokenCreated = 0; while (updateUserEmailCreatedList.size() > indexUpdateUserList || userEmailTokenResponseDto.userId.size() > indexUserId || userEmailTokenResponseDto.userName.size() > indexUserName || userEmailTokenResponseDto.userEmailToken.size() > indexUserEmailToken || userEmailTokenResponseDto.userEmailTokenExpiers.size() > indexUserEmailTokenExpire || userEmailTokenResponseDto.userEmailTokenCreated.size() > indexUserEmailTokenCreated) { // UserEntity userEntity = new UserEntity(); // UserPrimaryKey userPrimaryKey = new UserPrimaryKey(); if (Objects.isNull(updateUserEmailCreatedList) || updateUserEmailCreatedList.isEmpty()) { break; } if (Objects.isNull(updateUserEmailCreatedList.get(indexUpdateUserList))) { indexUpdateUserList++; indexUserId++; indexUserName++; indexUserEmailToken++; indexUserEmailTokenExpire++; indexUserEmailTokenCreated++; continue; } if (Objects.isNull(updateUserEmailCreatedList.get(indexUpdateUserList).getUserId()) || Objects.isNull( updateUserEmailCreatedList.get(indexUpdateUserList).getUserName())) { continue; } if (updateUserEmailCreatedList.get(indexUpdateUserList).getUserId() == Integer .valueOf(userEmailTokenResponseDto.userId.get(indexUserId)) && updateUserEmailCreatedList.get(indexUpdateUserList).getUserName() .equals(userEmailTokenResponseDto.userName.get(indexUserName))) { // 更新する項目 updateUserEmailCreatedList.get(indexUpdateUserList).setUserEmailToken(userEmailTokenResponseDto.userEmailToken.get(indexUserEmailToken)); updateUserEmailCreatedList.get(indexUpdateUserList).setUserEmailTokenExpires( userEmailTokenResponseDto.userEmailTokenExpiers.get(indexUserEmailTokenExpire)); updateUserEmailCreatedList.get(indexUpdateUserList).setUserEmailTokenCreated(Timestamp .valueOf(userEmailTokenResponseDto.userEmailTokenCreated.get(indexUserEmailTokenCreated))); updateUserEntityList.add(updateUserEmailCreatedList.get(indexUpdateUserList)); indexUpdateUserList++; indexUserId++; indexUserName++; indexUserEmailToken++; indexUserEmailTokenExpire++; indexUserEmailTokenCreated++; } } ; if (indexUpdateUserList != 0 && indexUserId != 0 && indexUserName != 0 && indexUserEmailToken != 0 && indexUserEmailTokenExpire != 0 && indexUserEmailTokenCreated != 0) { // 更新 userRepository.saveAll(updateUserEntityList); } } }
■/QuartzProject/src/main/java/QuartzProject/schedule/QuartzScheduler.java
package QuartzProject.schedule; import static org.quartz.JobBuilder.*; import java.io.IOException; import java.util.Properties; import javax.annotation.PostConstruct; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 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; import org.springframework.scheduling.quartz.SpringBeanJobFactory; import QuartzProject.config.AutowiringSpringBeanJobFactory; @Configuration @ConditionalOnExpression("'${using.spring.schedulerFactory}'=='false'") public class QuartzScheduler { Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ApplicationContext applicationContext; @PostConstruct public void init() { logger.info("Hello world from Quartz..."); } @Bean public SpringBeanJobFactory springBeanJobFactory() { AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); logger.debug("Configuring Job factory"); jobFactory.setApplicationContext(applicationContext); return jobFactory; } @Bean public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory) throws SchedulerException { logger.debug("Getting a handle to the Scheduler"); Scheduler scheduler = factory.getScheduler(); scheduler.scheduleJob(job, trigger); logger.debug("Starting Scheduler threads"); scheduler.start(); return scheduler; } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setJobFactory(springBeanJobFactory()); factory.setQuartzProperties(quartzProperties()); return factory; } public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } @Bean public JobDetail jobDetail() { return newJob().ofType(OneDayJob.class).storeDurably().withIdentity(JobKey.jobKey("Qrtz_Job_Detail")) .withDescription("Invoke Sample Job service...").build(); } @Bean public Trigger trigger(JobDetail job) { int frequencyInSec = 10; logger.info("Configuring trigger to fire every {} seconds", frequencyInSec); // return newTrigger().forJob(job).withIdentity(TriggerKey.triggerKey("Qrtz_Trigger")) // .withDescription("Sample trigger") // .withSchedule(simpleSchedule().withIntervalInSeconds(frequencyInSec).repeatForever()).build(); return TriggerBuilder.newTrigger() .forJob(job) .withIdentity("Qrtz_Trigger", "group1") // .startAt(DateBuilder.tomorrowAt(0, 0, 0)) // 0:00:00 tomorrow // .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule() // .withIntervalInMonths(2)) // interval is set in calendar 2 months .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1000) .repeatForever()) .build(); } }
■/QuartzProject/src/main/java/QuartzProject/schedule/OneDayJob.java
package QuartzProject.schedule; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import QuartzProject.service.UserServiceImpl; @Component public class OneDayJob implements Job { @Autowired private UserServiceImpl userServiceImpl; @Override public void execute(JobExecutionContext context) throws JobExecutionException { // userServiceImpl.updateUserMailToken(userServiceImpl.searchAll()); } }
■/QuartzProject/src/main/java/QuartzProject/schedule/SpringQuartzScheduler.java
package QuartzProject.schedule; import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.quartz.JobDetail; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.quartz.QuartzDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; 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.JobDetailFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import org.springframework.scheduling.quartz.SpringBeanJobFactory; import QuartzProject.config.AutowiringSpringBeanJobFactory; @Configuration @EnableAutoConfiguration @ConditionalOnExpression("'${using.spring.schedulerFactory}'=='true'") public class SpringQuartzScheduler { Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ApplicationContext applicationContext; @PostConstruct public void init() { logger.info("Hello world from Spring..."); } @Bean public SpringBeanJobFactory springBeanJobFactory() { AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); logger.debug("Configuring Job factory"); jobFactory.setApplicationContext(applicationContext); return jobFactory; } @Bean public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setConfigLocation(new ClassPathResource("/quartz.properties")); logger.debug("Setting the Scheduler up"); schedulerFactory.setJobFactory(springBeanJobFactory()); schedulerFactory.setJobDetails(job); schedulerFactory.setTriggers(trigger); // Comment the following line to use the default Quartz job store. schedulerFactory.setDataSource(quartzDataSource); return schedulerFactory; } @Bean public JobDetailFactoryBean jobDetail() { JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); jobDetailFactory.setJobClass(OneDayJob.class); jobDetailFactory.setName("Qrtz_Job_Detail"); jobDetailFactory.setDescription("Invoke Sample Job service..."); jobDetailFactory.setDurability(true); return jobDetailFactory; } @Bean public SimpleTriggerFactoryBean trigger(JobDetail job) { SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(job); int frequencyInSec = 10; logger.info("Configuring trigger to fire every {} seconds", frequencyInSec); trigger.setRepeatInterval(frequencyInSec * 1000); trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); trigger.setName("Qrtz_Trigger"); return trigger; } @Bean @QuartzDataSource @ConfigurationProperties(prefix = "spring.datasource") public DataSource quartzDataSource() { return DataSourceBuilder.create().build(); } }
■/QuartzProject/src/main/java/QuartzProject/Library.java
/* * This Java source file was generated by the Gradle 'init' task. */ package QuartzProject; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class Library { public boolean someLibraryMethod() { return true; } public static void main(String[] args) { //new SpringApplicationBuilder(Library.class).bannerMode(Mode.OFF).run(args); SpringApplication.run(Library.class, args); // JobDetail job = JobBuilder.newJob(OneDayJob.class) // .withIdentity("job1", "group1") // .build(); // // Trigger trigger = TriggerBuilder.newTrigger() // .withIdentity("trigger1", "group1") //// .startAt(DateBuilder.tomorrowAt(0, 0, 0)) // 0:00:00 tomorrow //// .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule() //// .withIntervalInMonths(2)) // interval is set in calendar 2 months // .startNow() // .withSchedule(SimpleScheduleBuilder.simpleSchedule() // .withIntervalInSeconds(1000) // .repeatForever()) // .build(); // // Scheduler scheduler = null; // try { // scheduler = StdSchedulerFactory.getDefaultScheduler(); // scheduler.start(); // if (scheduler.checkExists(job.getKey())){ // scheduler.deleteJob(job.getKey()); // } // scheduler.scheduleJob(job, trigger); // } catch (SchedulerException e) { // // TODO 自動生成された catch ブロック // e.printStackTrace(); // } // // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // // TODO 自動生成された catch ブロック // e.printStackTrace(); // } // // try { // scheduler.shutdown(); // } catch (SchedulerException e) { // // TODO 自動生成された catch ブロック // throw new RuntimeException(e); // } // System.out.println("Library#main end"); } }
⇧ ファイルが編集できたら、保存し、「実行(R)」>「Spring Boot アプリケーション」を選択でプログラムを実行。
「バッチ処理」で、永久に処理が走り続けるみたいな感じになってるので、対象の処理が実行されたか「コンソール」で確認し、実施されてる場合は、「停止」ボタンで「Spring Boot アプリケーション」を手動で停止します。
テーブルの内容を見てみると、
⇧ 期待したように更新されてるようです。
ただ、いまいまの作りだと、複数の「バッチ処理」が処理できるようになってないので、要調査ですかね。
それにしても、「Spring Framework」って本当に、必要なライブラリや、「依存性の注入(DI:Dependency Injection)」とかのために自分で何か追加しないといけないのかそうでないのかが分かり辛い...
「spring-boot-starter-〇〇」って「依存関係」がどこまでしてくれるのかハッキリ説明してくれてれば、こんな迷走しなくても良くなる気がするんですけどね...
毎回、モヤモヤ感が半端ない...
今回はこのへんで。
NG集
「@Autowired」アノテーションを付与したフィールドが「依存性の注入(DI:Dependency Injection)」されなくて、「NullPointerException」が起こるという事象が起きて四苦八苦しました。
まぁ、結局、原因がよく分からんかったのだけど
なんか、「MVN Repository」で、
⇧ みたいな説明があったんで、「spring-boot-starter-〇〇」シリーズについては、「spring-boot-starter」の「spring-core」が連携されて「依存性の注入(DI:Dependency injection)」とかできるんかな、と思い込んでいたので、「spring-boot-starter-quartz」って依存関係を利用すると「quartz」とかを「Spring Framework」の世界の「依存性の注入(DI:Dependency injection)」に適応してくれるのかな?、と思ってたですよね。
で、公式じゃない情報だけど、「@Autowired」については、
The @Autowired annotation marks a Constructor, Setter method, Properties and Config() method as to be autowired that is ‘injecting beans'(Objects) at runtime by Spring Dependency Injection mechanism which is clearly depicted from he image below as shown:
⇧ みたいな感じで、「Spring Dependency Injection mechanism」によって実行時に「Bean」が利用できる状態になっている(インスタンス化されてる)ってことを実現してくれるものだという認識なんだけど、「Spring Dependency Injection mechanism」は「spring-core」が担ってると思うので、「spring-boot-starter-〇〇」シリーズであるはずの「spring-boot-starter-quartz」についても良しなに「@Autowired」とか機能するような仕組みにしてくれてるんだろうな、って期待するじゃないですか?
なので、てっきり「@Autowired」とか機能するものと思ってたのですが、
⇧ どうも、「Spring Boot」のドキュメントとかを見ても、「Quartz」関連のクラスとか継承してる中では、そもそも「@Autowired」ができないのか、「@Autowired」を使ってる実装例が見当たらない...
嫌な予感しかしないのだが...
で、さらに、stackoverflowとかで同様の問題を上げてる人がおり、
⇧ 見た感じ、
- extends SpringBeanJobFactory
- implements ApplicationContextAware
の「クラス」の継承と「インターフェイス」の実装をした「クラス」を用意しておかないと、「Spring Framework」の世界のルールが適応されないんじゃなか説があるらしい...。
まぁ、他にも、いろいろ追加したんだけどね...
結局、自分で「依存性の注入(DI:Dependency injection)」のためにいろいろ追加する以外の方法が分からんかったけど...
スッキリせんわ...
2022年2月13日(日)追記:↓ ここから
「spring-boot-autoconfigure」の依存関係を追加すれば、「SchedulerFactoryBean」クラスを継承したクラスを作成しなくてもQuartzの機能を使えました。
⇧ 手前味噌で恐縮ですが、上記の記事でまとめております。
それにしても、「spring-boot-autoconfigure」を依存関係に追加してQuartzを使ってる情報がネットで皆無だったのが気になりますな...
2022年2月13日(日)追記:↑ ここまで