JPA(Java Persistence API)でリレーションの無いテーブル同士のデータをどう取得するか

f:id:ts0818:20220321132703j:plain

www.publickey1.jp

eBPF(Extended Berkeley Packet Filter)はクラウドネイティブ関連で最も注目されている技術の1つです。

Linuxカーネルを拡張する「eBPF」のWindows対応を進めるマイクロソフト。eBPFの代表的なアプリ「Cilium」をWindowsへ移植 - Publickey

⇧ へぇ~、知らなんだ。

isovalent.com

Service mesh is a concept describing the requirements of modern cloud native applications with regards to communication, visibility, and security. Current implementations of this concept involve running sidecar proxies in each workload or pod. This is a pretty inefficient way of solving these requirements. In this post, we will look at an alternative to the sidecar model that provides a transparent service mesh with high efficiency at low complexity, with the help of eBPF.

https://isovalent.com/blog/post/2021-12-08-ebpf-servicemesh

⇧「Sidecar pattern」の代替として注目されてるらしいですね。

例の如く冒頭から関係ない話でしたが、今回もJavaについてです。

レッツトライ~。

JPAJava Persistence API)とは?

Wikipediaさんに聞いてみよう。

Java Persistence APIJPA)とは、関係データベースのデータを扱うJava SEおよびJakarta EE(旧・Java EE)のアプリケーションを開発するためのJavaフレームワークである。

Java Persistence API - Wikipedia

JPAは、以下の3つの部分から成る。

  • API(javax.persistence パッケージで定義されている)
  • Java Persistence Query Language
  • オブジェクト/関係メタデータ

Java Persistence API - Wikipedia

⇧ とあって、「RDBMS(Relational DataBase Management System)」とJavaとの間でデータを扱えるようにしたJavaフレームワークの仕様ですと。

開発の背景

EJB 2.0までのEntity Beanなどのエンタープライズビーンは、重すぎて複雑すぎ、Java EEアプリケーションサーバでしか使えないという問題があった。このため、その代替としてData Access Objectオープンソースフレームワークを使った軽量の永続性オブジェクトが使われることが多くなった。

Java Persistence API - Wikipedia

そのようなサードパーティーの永続性フレームワークの機能を集約したのがJava Persistence APIであり、HibernateTopLinkのようなプロジェクトも現在ではJava Persistence APIを実装している。

Java Persistence API - Wikipedia

⇧ とあって、「Hibernate」のような「ORM(Object-relational mapping)」ライブラリでも利用されているみたい。

JPAJava Persistence API)」の仕様のドキュメントは、

⇧ 公開されてるっぽいのだけど、大長編につき、むちゃくちゃ読む気力が出てこないのよね...

JPAJava Persistence API)の仕組みなど

JPAJava Persistence API)」の仕様を実装したものの1つに「Apache OpenJPA」というものがあるらしく、そのドキュメントによると、「JPAJava Persistence API)」は

openjpa.apache.org

⇧ のようなアーキテクチャになっているようですと。

JPAJava Persistence API)」の仕様のドキュメントによると、

The term "persistence provider runtime" refers to the runtime environment of the persistence implementation. In Java EE environments, this may be the Java EE container or a third-party persistence provider implementation integrated with it.    

⇧「persistence provider runtime」の用語についての説明が出てくるんだけども、そもそも「persistence provider」ってところがまず分からないんだが...そこちゃんと説明して欲しいんだけど...

同様の疑問を持たれた方がおられました。

stackoverflow.com

To provide further explanations, JPA is an API specified in the frame of the JCP as an answer to a request (e.g JSR 338 for JPA 2.1).

Several implementations of that specification exist, the main are:

In the frame of the Java platform, when a standard API is implemented, this is specified via a system called SPI (for Service Provider Interfaces). Each "vendor" of an implementation of an API has to provide a specific component which is a single interface as a starting point for the implementing classes. This is the origin of the provider word.

https://stackoverflow.com/questions/27420513/what-is-a-jpa-provider

⇧「provider」って用語は、Javaの標準API以外のライブラリをJavaで利用できるようにする「SPI(Service Provider Intefaces)」が由来だそうな。

「SPI(Service Provider Intefaces)」はと言うと、

Service provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.

https://en.wikipedia.org/wiki/Service_provider_interface

From Java documentation:

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application's class path or by some other platform-specific means.

https://en.wikipedia.org/wiki/Service_provider_interface

⇧ というように、「サードパーティ(第三者)」によって拡張されることを意図したAPIということらしいので、Javaの標準API以外のものを指すってことかと。

だいぶ話が脱線しましたが、「persistence provider」というのは、「JPAJava Persistence API)」の「SPI(Service Provider Intefaces)」になるということだと思うので、「サードパーティ(第三者)」による実装のことで、

のようなものが「persistence provider」の代表的なものになるらしいですと、分かり辛い...

というか、Javaって暗黙知の要素が多過ぎるんだが...

「JSR」で議論してくれるのは良いのだけど、せめてドキュメントで使用してる用語については、用語集的なものを整備してくれると助かるんですけど、というか議論するだけで満足してるのか分からんけれどもドキュメント作るのがおざなりになってる気がして仕方ないんだけど...

まぁ、まとめると、「JPAJava Persistence API)」って言った場合、「persistence provider」が必ず絡んできてますと。

ちなみに、「Spring Data JPA」なんかだと、

github.com

Simplifies the development of creating a JPA-based data access layer.

https://github.com/spring-projects/spring-data-jpa

JPA-basedと言っているので、「JPAJava Persistence API)」で利用できる「persistence provider」は全部使えるってことなんかな?

特に使えない「persistence provider」について言及ないですし。

JPAJava Persistence API)はqueryの書き方が様々

このあたりも、情報がカオスなんだけど、

定義した DTO は、JPQL、Criteria、ネイティブ問合せで投影として使用できます。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java_MJ18_JPAPatterns.pdf

⇧ 上記のPDF資料によると、

  • JPQL(Java Persistence Query Lanuage)
  • Criteria
  • Native Query

の3つの方法がある模様、ただ、これが「JPAJava Persistence API)」のqueryの全量なのかがハッキリしない...

Oracleさんのブログによると、「JPAJava Persistence API)」のqueryは、

⇧ 4種類が全量ってことみたい、「JPQL(Java Persistence Query Lanuage)」が、

  • 動的クエリ
  • 名前付きクエリ

の2つに分かれるってことみたいね。

なので、「JPAJava Persistence API)」のqueryの全量は

  • JPQL(Java Persistence Query Lanuage)
    • 動的クエリ
    • 名前付きクエリ
  • Criteria
  • Native Query

の4種類ということになるらしい。

JPAJava Persistence API)でリレーションの無いテーブル同士のデータをどう取得するか

ようやっと、本日のお題。

リレーションがない、つまり「外部キー」のないようなテーブル同士のデータをどう取得するか。

おそらく、マスター系のテーブルとかだとリレーションが無いことが多いのかなと。

例えば、『四国お遍路記録』みたいなケースを考えてみる。

そういえば、ローマ字の表記揺れの話で、

blog.identitymarket.net

 九州のローマ字表記は「Kyushu」と「Kyusyu」のどちらが正しいのか?
 こちらに移って十数年になるが、いまだにこれが分からない。

「Kyushu」と「Kyusyu」 | identity market on BLOG

⇧ 上記サイト様の話が興味深いです。

今回、日本のエリアとして

現代の日本の初等教育では、国内を都道府県単位で「北海道」「東北」「関東」「中部」「近畿」「中国・四国」「九州」に分ける「七地方区分」、または中国四国を分離した「八地方区分」が用いられることが多い。

日本の地域 - Wikipedia

しかしながら、いずれも法的根拠は明確ではなく、「そもそも『〜地方』といわれる範囲に、法律上の明確な定義はない(総務省)」とされる

日本の地域 - Wikipedia

国の出先機関である地方支分部局も、この区分に従わないものがほとんどである(地方整備局地方農政局など)。

日本の地域 - Wikipedia

⇧ 上記のWikipediaの情報を参考にしたのだけど、「七地方区分」と「その他の区分」で重複してる部分があって、上手く分類しきれてないので、いろいろ適当な部分がありますので悪しからず。

脱線しましたが、テーブルの作成。

CREATE TABLE public.area (
  id INT,
  name_area VARCHAR(20) NOT NULL,
  kana_name_area VARCHAR(50) NOT NULL,
  rome_name_area VARCHAR(50) NOT NULL,
  created TIMESTAMP(6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP(6) WITH TIME ZONE,
  is_deleted BOOLEAN DEFAULT FALSE,
  PRIMARY KEY(id)
);

CREATE TABLE public.area_kbn (
  id INT,
  name_area_kbn VARCHAR(20) NOT NULL,
  kana_name_area_kbn VARCHAR(50) NOT NULL,
  rome_name_area_kbn VARCHAR(50) NOT NULL,
  area_id INT NOT NULL,
  created TIMESTAMP(6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP(6) WITH TIME ZONE,
  is_deleted BOOLEAN DEFAULT FALSE,
  PRIMARY KEY(id),
  FOREIGN KEY(area_id) REFERENCES public.area(id)
);

CREATE TABLE public.prefectures (
  id INT,
  name_prefecture VARCHAR(20) NOT NULL,
  kana_name_prefecture VARCHAR(50) NOT NULL,
  rome_name_prefecture VARCHAR(50) NOT NULL,
  name_prefectural_capital VARCHAR(20) NOT NULL,
  kana_name_prefectural_capital VARCHAR(50) NOT NULL,
  rome_name_prefectural_capital VARCHAR(50) NOT NULL,
  area_id INT NOT NULL,
  created TIMESTAMP(6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP(6) WITH TIME ZONE,
  is_deleted BOOLEAN DEFAULT FALSE,
  PRIMARY KEY(id),
  FOREIGN KEY(area_id) REFERENCES public.area(id)
);

CREATE TABLE public.shikoku_ohenro (
  id SERIAL,
  name_fudasyo VARCHAR(20) NOT NULL,
  kana_name_fudasyo VARCHAR(50) NOT NULL,
  rome_name_fudasyo VARCHAR(50) NOT NULL,
  name_temple VARCHAR(20) NOT NULL,
  kana_name_temple VARCHAR(50) NOT NULL,
  rome_name_temple VARCHAR(50) NOT NULL,
  prefectures_id INT NOT NULL,
  created TIMESTAMP(6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP(6) WITH TIME ZONE,
  is_deleted BOOLEAN DEFAULT FALSE,
  PRIMARY KEY(id),
  FOREIGN KEY(prefectures_id) REFERENCES public.prefectures(id)
);

CREATE TABLE shikoku_ohenro_user (
  id SERIAL,
  last_name VARCHAR(50) NOT NULL,
  first_name VARCHAR(50) NOT NULL,
  rome_last_name VARCHAR(50) NOT NULL,
  rome_first_name VARCHAR(50) NOT NULL,
  ohenro_ids TEXT,
  created TIMESTAMP(6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP(6) WITH TIME ZONE,
  is_deleted BOOLEAN DEFAULT FALSE,
  PRIMARY KEY(id)
);

CREATE TABLE shikoku_ohenro_log (
  id SERIAL,
  user_id BIGINT NOT NULL,
  ohenro_id BIGINT NOT NULL,
  created TIMESTAMP(6) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated TIMESTAMP(6) WITH TIME ZONE,
  is_deleted BOOLEAN DEFAULT FALSE,
  PRIMARY KEY(id)
);

続いて、データを投入。

-- エリアデータ
INSERT INTO area (id, name_area, kana_name_area, rome_name_area)
 VALUES(1, '北海道地方', 'ほっかいどうちほう', 'hokkaido chihou')
 ,(2, '東北地方', 'とうほくちほう', 'touhoku chihou')
 ,(3, '関東地方', 'かんとうちほう', 'kantou chihou')
 ,(4, '中部地方', 'ちゅうぶちほう', 'chubu chihou')
 ,(5, '近畿地方', 'きんきちほう', 'kinki chihou')
 ,(6, '中国・四国地方', 'ちゅうごく・しこくちほう', 'chugoku sikoku chihou')
 ,(7, '九州地方', 'きゅうしゅうちほう', 'kyushu chihou');

-- エリア区分データ
INSERT INTO area_kbn (id, name_area_kbn, kana_name_area_kbn, rome_name_area_kbn, area_id)
 VALUES(1, '道北', 'どうほく', 'douhoku', 1)
 ,(2, '道東', 'どうとう', 'doutou', 1)
 ,(3, '道央', 'どうおう', 'douou', 1)
 ,(4, '道南', 'どうなん', 'dounan', 1)
 ,(5, '北東北', 'きたとうほく', 'kitatouhoku', 2)
 ,(6, '南東北', 'みなみとうほく', 'minamitouhoku', 2)
 ,(7, '北関東', 'きたかんとう', 'kitakantou', 3)
 ,(8, '南関東', 'みなみかんとう', 'minamikantou', 3)
 ,(9, '甲信越', 'こうしんえつ', 'koushinetsu', 4)
 ,(10, '北陸', 'ほくりく', 'hokuriku', 4)
 ,(11, '東海', 'とうかい', 'toukai', 4)
 ,(12, '関西', 'かんさい', 'kansai', 5)
 ,(13, '山陰', 'さんいん', 'sanin', 6)
 ,(14, '山陽', 'さんよう', 'sanyou', 6)
 ,(15, '北四国', 'きたしこく', 'kitashikoku', 6)
 ,(16, '南四国', 'みなみしこく', 'minamishikoku', 6)
 ,(17, '北部九州', 'ほくぶきゅうしゅう', 'hokubukyushu', 7)
 ,(18, '南九州', 'みなみきゅうしゅう', 'minamikyushu', 7)
 ,(19, '沖縄', 'おきなわ', 'okinawa', 7);

-- 都道府県データ
INSERT INTO prefectures (id, name_prefecture, kana_name_prefecture, rome_name_prefecture, name_prefectural_capital, kana_name_prefectural_capital, rome_name_prefectural_capital, area_id)
 VALUES(1,'北海道', 'ほっかいどう', 'hokkaido','札幌', 'さっぽろ', 'sapporo', 1)
 ,(2,'青森県', 'あおもりけん', 'aomoriken','青森', 'あおもり', 'aomori', 2)
 ,(3,'岩手県', 'いわてけん', 'iwateken','盛岡', 'もりおか', 'morioka', 2)
 ,(4,'宮城県', 'みやぎけん', 'miyagiken','仙台', 'せんだい', 'sendai', 2)
 ,(5,'秋田県', 'あきたけん', 'akitaken','秋田', 'あきた', 'akita', 2)
 ,(6,'山形県', 'やまがたけん', 'yamagataken','山形', 'やまがた', 'yamagata', 2)
 ,(7,'福島県', 'ふくしまけん', 'fukushimaken','福島', 'ふくしま', 'fukushima', 2)
 ,(8,'茨城県', 'いばらきけん', 'ibarakiken','水戸', 'みと', 'mito', 3)
 ,(9,'栃木県', 'とちぎけん', 'tochigiken','宇都宮', 'うつのみや', 'utsunomiya', 3)
 ,(10,'群馬県', 'ぐんまけん', 'gunmaken','前橋', 'まえばし', 'maebashi', 3)
 ,(11,'埼玉県', 'さいたまけん', 'saitamaken','さいたま', 'さいたま', 'saitama', 3)
 ,(12,'千葉県', 'ちばけん', 'chibaken','千葉', 'ちば', 'chiba', 3)
 ,(13,'東京都', 'とうきょうと', 'tokyoto','新宿', 'しんじゅく', 'shinjyuku', 3)
 ,(14,'神奈川県', 'かながわけん', 'kanagawaken','横浜', 'よこはま', 'yokohama', 3)
 ,(15,'新潟県', 'にいがたけん', 'nigataken','新潟', 'にいがた', 'niigata', 4)
 ,(16,'富山県', 'とやまけん', 'toyamaken','富山', 'とやま', 'toyama', 4)
 ,(17,'石川県', 'いしかわけん', 'ishikawaken','金沢', 'かなざわ', 'kanazawa', 4)
 ,(18,'福井県', 'ふくいけん', 'fukuiken','福井', 'ふくい', 'fukui', 4)
 ,(19,'山梨県', 'やまなしけん', 'yamanashiken','甲府', 'こうふ', 'koufu', 4)
 ,(20,'長野県', 'ながのけん', 'naganoken','長野', 'ながの', 'nagano', 4)
 ,(21,'岐阜県', 'ぎふけん', 'gifuken','岐阜', 'ぎふ', 'gifu', 4)
 ,(22,'静岡県', 'しずおかけん', 'shizuokaken','静岡', 'しずおか', 'shizuoka', 4)
 ,(23,'愛知県', 'あいちけん', 'aichiken','名古屋', 'なごや', 'nagoya', 4)
 ,(24,'三重県', 'みえけん', 'mieken','津', 'つ', 'tsu', 5)
 ,(25,'滋賀県', 'しがけん', 'shigaken','大津', 'おおつ', 'ootsu', 5)
 ,(26,'京都府', 'きょうとふ', 'kyotofu','京都', 'きょうと', 'kyoto', 5)
 ,(27,'大阪府', 'おおさかふ', 'oosakafu','大阪', 'おおさか', 'oosaka', 5)
 ,(28,'兵庫県', 'ひょうごけん', 'hyogoken','神戸', 'こうべ', 'koube', 5)
 ,(29,'奈良県', 'ならけん', 'naraken','奈良', 'なら', 'nara', 5)
 ,(30,'和歌山県', 'わかやまけん', 'wakayamaken','和歌山', 'わかやま', 'wakayama', 5)
 ,(31,'鳥取県', 'とっとりけん', 'tottoriken','鳥取', 'とっとり', 'tottori', 6)
 ,(32,'島根県', 'しまねけん', 'shimaneken','松江', 'まつえ', 'matsue', 6)
 ,(33,'岡山県', 'おかやまけん', 'okayamaken','岡山', 'おかやま', 'okayama', 6)
 ,(34,'広島県', 'ひろしまけん', 'hiroshimaken','広島', 'ひろしま', 'hiroshima', 6)
 ,(35,'山口県', 'やまぐちけん', 'yamaguchiken','山口', 'やまぐち', 'yamaguchi', 6)
 ,(36,'徳島県', 'とくしまけん', 'tokushimaken','徳島', 'とくしま', 'tokushima', 6)
 ,(37,'香川県', 'かがわけん', 'kagawaken','高松', 'たかまつ', 'takamatsu', 6)
 ,(38,'愛媛県', 'えひめけん', 'ehimeken','松山', 'まつやま', 'matsuyama', 6)
 ,(39,'高知県', 'こうちけん', 'kouchiken','高知', 'こうち', 'kouchi', 6)
 ,(40,'福岡県', 'ふくおかけん', 'fukuokaken','福岡', 'ふくおか', 'fukuoka', 7)
 ,(41,'佐賀県', 'さがけん', 'sagaken','佐賀', 'さが', 'saga', 7)
 ,(42,'長崎県', 'ながさきけん', 'nagasakiken','長崎', 'ながさき', 'nagasaki', 7)
 ,(43,'熊本県', 'くまもとけん', 'kumamotoken','熊本', 'くまもと', 'kumamoto', 7)
 ,(44,'大分県', 'おおいたけん', 'ooitaken','大分', 'おおいた', 'ooita', 7)
 ,(45,'宮崎県', 'みやざきけん', 'miyazakiken','宮崎', 'みやざき', 'miyazaki', 7)
 ,(46,'鹿児島県', 'かごしまけん', 'kagoshimaken','鹿児島', 'かごしま', 'kagoshima', 7)
 ,(47,'沖縄県', 'おきなわけん', 'okinawaken','那覇', 'なは', 'naha', 7);

-- お遍路データ
INSERT INTO shikoku_ohenro (id, name_fudasyo, kana_name_fudasyo, rome_name_fudasyo, name_temple, kana_name_temple, rome_name_temple, prefectures_id)
 VALUES(1, '第1番札所','だいいちばんふだしょ', 'daiichibanfudasyo', '霊山寺', 'りょうぜんじ', 'ryozenji', 36)
 ,(2, '第2番札所','だいにばんふだしょ', 'dainibanfudasyo', '極楽寺', 'ごくらくじ', 'gokurakuji', 36)
 ,(3, '第3番札所','だいさんばんふだしょ', 'daisanbanfudasyo', '金泉寺', 'こんせんじ', 'konsenji', 36)
 ,(4, '第4番札所','だいよんばんふだしょ', 'daiyonbanfudasyo', '大日寺', 'だいにちじ', 'dainichiji', 36)
 ,(5, '第5番札所','だいごばんふだしょ', 'daiigobanfudasyo', '地蔵寺', 'じぞうじ', 'jizouji', 36)
 ,(6, '第6番札所','だいろくばんふだしょ', 'dairokubanfudasyo','安楽寺', 'あんらくじ', 'anrakuji', 36)
 ,(7, '第7番札所','だいななばんふだしょ', 'dainanabanfudasyo','十楽寺', 'じゅうらくじ', 'jyurakuji', 36)
 ,(8, '第8番札所','だいはちばんふだしょ', 'daihachibanfudasyo','熊谷寺', 'くまだにじ ', 'kumadaniji', 36)
 ,(9, '第9番札所','だいきゅうばんふだしょ', 'daikyubanfudasyo','法輪寺', 'ほうりんじ', 'hourinji', 36)
 ,(10,'第10番札所','だいじゅうばんふだしょ', 'daijyubanbanfudasyo','切幡寺', 'きりはたじ', 'kiriharadai', 36)
 ,(11,'第11番札所','だいじゅういちばんふだしょ', 'daijyuichibanfudasyo', '藤井寺', 'ふじいでら', 'fujiidera', 36)
 ,(12,'第12番札所','だいじゅうにばんふだしょ', 'daijyuuibanfudasyo','焼山寺', 'しょうざんじ', 'syouzanji', 36)
 ,(13,'第13番札所','だいじゅうさんばんふだしょ', 'daijyusanbanfudasyo','大日寺', 'だいにちじ', 'dainichiji', 36)
 ,(14,'第14番札所','だいじゅうよんばんふだしょ', 'daijyuyonbanfudasyo','常楽寺', 'じょうらくじ', 'jyourakuji', 36)
 ,(15,'第15番札所','だいじゅうごばんふだしょ', 'daijyugobanfudasyo','国分寺', 'こくぶんじ', 'kokubunji', 36)
 ,(16,'第16番札所','だいじゅうろくばんふだしょ', 'daijyurokubanfudasyo','観音寺', 'かんおんじ', 'kanonji', 36)
 ,(17,'第17番札所','だいじゅうななばんふだしょ', 'daijyunanabanfudasyo','井戸寺', 'いどじ', 'idoji', 36)
 ,(18,'第18番札所','だいじゅうはちばんふだしょ', 'daijyuhachibanfudasyo','恩山寺', 'おんざんじ', 'onzanji', 36) 
 ,(19,'第19番札所','だいじゅうきゅうばんふだしょ', 'daijyukyubanfudasyo','立江寺', 'りつえじ', 'ritsuenji', 36)
 ,(20,'第20番札所','だいにじゅうばんふだしょ', 'dainijyubanfudasyo','鶴林寺', ' かくりんじ', 'kakurinji', 36)
 ,(21,'第21番札所','だいにじゅういちばんふだしょ', 'dainijyuichibanfudasyo','太龍寺', 'たいりゅうじ', 'tairyuji', 36)
 ,(22,'第22番札所','だいにじゅうにばんふだしょ', 'dainijyunibanfudasyo', '平等寺', 'びょうどうじ', 'byoudouji', 36)
 ,(23,'第23番札所','だいにじゅうさんばんふだしょ', 'dainijyusanbanfudasyo','薬王寺', 'やくおうじ', 'yakuouji', 36)
 ,(24,'第24番札所','だいにじゅうよんばんふだしょ', 'dainijyuyonbanfudasyo', '最御崎寺', 'ほつみさきじ', 'hotsumisakiji', 39)
 ,(25,'第25番札所','だいにじゅうごばんふだしょ', 'dainijyugobanfudasyo', '津照寺', 'しんしょうじ', 'shinsyouji', 39)
 ,(26,'第26番札所','だいにじゅうろくばんふだしょ', 'dainijyurokubanfudasyo','金剛頂寺', 'こんごうちょうじ', 'kongoutyouji', 39)
 ,(27,'第27番札所','だいにじゅうななばんふだしょ', 'dainijyunanabanfudasyo','神峰寺', 'こうのみねじ', 'kounomineji', 39)
 ,(28,'第28番札所','だいにじゅうはちばんふだしょ', 'dainijyuhachibanfudasyo','大日寺', 'だいにちじ', 'dainichiji', 39) 
 ,(29,'第29番札所','だいにじゅうきゅうばんふだしょ', 'dainijyukyubanfudasyo','国分寺', 'こくぶんじ', 'kokubunji', 39)
 ,(30,'第30番札所','だいさんじゅうばんふだしょ', 'daisanjyubanfudasyo','善楽寺', 'ぜんらくじ', 'zenrakuji', 39)
 ,(31,'第31番札所','だいさんじゅういちばんふだしょ', 'daisanjyuichibanfudasyo','竹林寺', 'ちくりんじ', 'chikurinji', 39)
 ,(32,'第32番札所','だいさんじゅうにばんふだしょ', 'daisanjyunibanfudasyo','禅師峰寺', 'ぜんじぶじ', 'zenjibuji', 39)
 ,(33,'第33番札所','だいさんじゅうさんばんふだしょ', 'daisanjyusanbanfudasyo','雪蹊寺', 'せっけいじ', 'sekkeiji', 39)
 ,(34,'第34番札所','だいさんじゅうよんばんふだしょ', 'daisanjyuyonbanfudasyo','種間寺', 'たねまじ', 'tamaneji', 39)
 ,(35,'第35番札所','だいさんじゅうごばんふだしょ', 'daisanjyugobanfudasyo','清滝寺', 'きよたきじ', 'kiyotakiji', 39)
 ,(36,'第36番札所','だいさんじゅうろくばんふだしょ', 'daisanjyurokubanfudasyo','青龍寺', 'しょうりゅうじ', 'syouryuji', 39)
 ,(37,'第37番札所','だいさんじゅうななばんふだしょ', 'daisanjyunanabanfudasyo','岩本寺', 'いわもとじ', 'iwamotoji', 39)
 ,(38,'第38番札所','だいさんじゅうはちばんふだしょ', 'daisanjyuhachibanfudasyo','金剛福寺', 'こんごうふくじ', 'kongoufukuji', 39)
 ,(39,'第39番札所','だいさんじゅうきゅうばんふだしょ', 'daisanjyukyubanfudasyo','延光寺', 'えんこうじ', 'enkouji', 39)
 ,(40,'第40番札所','だいよんじゅうばんふだしょ', 'daiyonjyubanfudasyo','観自在寺', 'かんじざいじ', 'kanjizaiji', 38)
 ,(41,'第41番札所','だいよんじゅういちばんふだしょ', 'daiyonjyuichibanfudasyo','龍光寺', 'りゅうこうじ', 'ryukouji', 38)
 ,(42,'第42番札所','だいよんじゅうにばんふだしょ', 'daiyonjyunibanfudasyo','佛木寺', 'ぶつもくじ', 'butsumokuji', 38)
 ,(43,'第43番札所','だいよんじゅうさんばんふだしょ', 'daiyonjyusanbanfudasyo','明石寺', 'あげいしじ', 'ageishiji', 38)
 ,(44,'第44番札所','だいよんじゅうよんばんふだしょ', 'daiyonjyuyonbanfudasyo','大宝寺', 'だいほうじ', 'daihouji', 38)
 ,(45,'第45番札所','だいよんじゅうごばんふだしょ', 'daiyonjyugobanfudasyo','岩屋寺', 'いわやじ', 'iwayaji', 38)
 ,(46,'第46番札所','だいよんじゅうろくばんふだしょ', 'daiyonjyurokubanfudasyo','浄瑠璃寺', 'じょうるりじ', 'jyoururiji', 38)
 ,(47,'第47番札所','だいよんじゅうななばんふだしょ', 'daiyonjyunanabanfudasyo','八坂寺', 'やさかじ', 'yasakaji', 38)
 ,(48,'第48番札所','だいよんじゅうはちばんふだしょ', 'daiyonjyuhachibanfudasyo','西林寺', 'さいりんじ', 'sairinji', 38)
 ,(49,'第49番札所','だいよんじゅうきゅうばんふだしょ', 'daiyonjyukyubanfudasyo','浄土寺', 'じょうどじ', 'jyoudoji', 38)
 ,(50,'第50番札所','だいごじゅうばんふだしょ', 'daigojyubanbanfudasyo','繁多寺', 'はんたじ', 'hantaji', 38)
 ,(51,'第51番札所','だいごじゅういちばんふだしょ', 'daigojyuichibanfudasyo','石手寺', 'いしてじ', 'ishiteji', 38)
 ,(52,'第52番札所','だいごじゅうにばんふだしょ', 'daigojyunibanfudasyo','太山寺', 'たいさんじ', 'taisaiji', 38)
 ,(53,'第53番札所','だいごじゅうさんばんふだしょ', 'daigojyusanbanfudasyo','円明寺', 'えんみょうじ', 'enmyouji', 38) 
 ,(54,'第54番札所','だいごじゅうよんばんふだしょ', 'daigojyuyonbanfudasyo','延命寺', 'えんめいじ', 'enmeiji', 38)
 ,(55,'第55番札所','だいごじゅうごばんふだしょ', 'daigojyugobanfudasyo','南光坊', 'なんこうぼう', 'nankoubou', 38)
 ,(56,'第56番札所','だいごじゅうろくばんふだしょ', 'daigojyurokubanfudasyo','泰山寺', 'たいさんじ', 'taisanji', 38)
 ,(57,'第57番札所','だいごじゅうななばんふだしょ', 'daigojyunanabanfudasyo','栄福寺', 'えいふくじ', 'eifukuji', 38)
 ,(58,'第58番札所','だいごじゅうはちばんふだしょ', 'daigojyuhachiichibanfudasyo','仙遊寺', 'せんゆうじ', 'senyuuji', 38)
 ,(59,'第59番札所','だいごじゅうきゅうばんふだしょ', 'daigojyukyubanfudasyo','国分寺', 'こくぶんじ', 'kokubunji', 38)
 ,(60,'第60番札所','だいろくじゅうばんふだしょ', 'dairokujyubanfudasyo','横峰寺', 'よこみねじ', 'yokomineji', 38)
 ,(61,'第61番札所','だいろくじゅういちばんふだしょ', 'dairokujyuichibanfudasyo','香園寺', 'こうおんじ', 'kouonji', 38)
 ,(62,'第62番札所','だいろくじゅうにばんふだしょ', 'dairokujyunibanfudasyo','宝寿寺', 'ほうじゅじ', 'houjyuji', 38)
 ,(63,'第63番札所','だいろくじゅうさんばんふだしょ', 'dairokujyusanbanfudasyo','吉祥寺', 'きっしょうじ', 'kissyouji', 38)
 ,(64,'第64番札所','だいろくじゅうよんばんふだしょ', 'dairokujyuyonbanfudasyo','前神寺', 'まえがみじ', 'maegamiji', 38)
 ,(65,'第65番札所','だいろくじゅうごばんふだしょ', 'dairokujyugobanfudasyo','三角寺', 'さんかくじ', 'sankakuji', 38)
 ,(66,'第66番札所','だいろくじゅうろくばんふだしょ', 'dairokujyurokubanfudasyo','雲辺寺', 'うんべんじ', 'unbenji', 37)
 ,(67,'第67番札所','だいろくじゅうななばんふだしょ', 'dairokujyunanabanfudasyo','大興寺', 'だいこうじ', 'daikouji', 37)
 ,(68,'第68番札所','だいろくじゅうはちばんふだしょ', 'dairokujyuhachibanfudasyo','神恵院', 'じんねいん', 'jinnein', 37)
 ,(69,'第69番札所','だいろくじゅうきゅうばんふだしょ', 'dairokujyukyubanfudasyo','観音寺', 'かんおんじ', 'kanonji', 37)
 ,(70,'第70番札所','だいななじゅうばんふだしょ', 'dainanajyubanfudasyo','本山寺', 'もとやまじ', 'motoyamaji', 37)
 ,(71,'第71番札所','だいななじゅういちばんふだしょ', 'dainanajyuichibanfudasyo','弥谷寺', 'いやだにじ', 'iyadaniji', 37)
 ,(72,'第72番札所','だいななじゅうにばんふだしょ', 'dainanajyunibanfudasyo','曼茶羅寺', 'まんだらじ', 'mandaraji', 37)
 ,(73,'第73番札所','だいななじゅうさんばんふだしょ', 'dainanajyusanbanfudasyo','出釈迦寺', 'しゅっしゃかじ', 'syussyakaji', 37)
 ,(74,'第74番札所','だいななじゅうよんばんふだしょ', 'dainanajyuyonbanfudasyo','甲山寺', 'こうやまじ', 'kouyamaji', 37)
 ,(75,'第75番札所','だいななじゅうごばんふだしょ', 'dainanajyugobanfudasyo','善通寺', 'ぜんつうじ', 'zentsuuji', 37)
 ,(76,'第76番札所','だいななじゅうろくばんふだしょ', 'dainanajyurokubanfudasyo','金倉寺', 'こうぞうじ', 'kouzouji', 37)
 ,(77,'第77番札所','だいななじゅうななばんふだしょ', 'dainanajyunanabanfudasyo','道隆寺', 'どうりゅうじ', 'douryuuji', 37)
 ,(78,'第78番札所','だいななじゅうはちばんふだしょ', 'dainanajyuhachibanfudasyo','郷照寺', 'ごうしょうじ', 'gousyouji', 37)
 ,(79,'第79番札所','だいななじゅうきゅうばんふだしょ', 'dainanajyukyubanfudasyo','高照院', 'こうしょういん', 'kousyouin', 37)
 ,(80,'第80番札所','だいはちじゅうばんふだしょ', 'daihachijyubanfudasyo','国分寺', 'こくぶんじ', 'kokubunji', 37)
 ,(81,'第81番札所','だいはちじゅういちばんふだしょ', 'daihachijyuichibanfudasyo','白峯寺', 'しろみねじ', 'shiromineji', 37)
 ,(82,'第82番札所','だいはちじゅうにばんふだしょ', 'daihachijyunibanfudasyo','根香寺', 'ねごろじ', 'negoroji', 37)
 ,(83,'第83番札所','だいはちじゅうさんばんふだしょ', 'daihachijyusanbanfudasyo','一宮寺', 'いちのみやじ', 'ichinomiya', 37)
 ,(84,'第84番札所','だいはちじゅうよんばんふだしょ', 'daihachijyuyonbanfudasyo','屋島寺', 'やしまじ', 'yashimaji', 37)
 ,(85,'第85番札所','だいはちじゅうごばんふだしょ', 'daihachijyugobanfudasyo','八栗寺', 'やくりじ', 'yakuriji', 37)
 ,(86,'第86番札所','だいはちじゅうろくばんふだしょ', 'daihachijyurokubanfudasyo','志度寺', 'しどじ', 'shidoji', 37)
 ,(87,'第87番札所','だいはちじゅうななばんふだしょ', 'daihachijyunanabanfudasyo','長尾寺', 'ながおじ', 'nagaoji', 37)
 ,(88,'第88番札所','だいはちじゅうはちばんふだしょ', 'daihachijyuhachibanfudasyo','大窪寺', 'おおくぼじ', 'ookuboji', 37);

-- お遍路ユーザーのデータ
INSERT INTO shikoku_ohenro_user (id, last_name, first_name, rome_last_name, rome_first_name, ohenro_ids)
 VALUES(1, '佐藤', '一', 'sato', 'hajime', '1,2,3,4,5')
 ,(2, '鈴木', '一郎', 'suzuki', 'ichiro', '81,82,83,84,85,86,87,88')
 ,(3, '高橋', '一登', 'takahashi', 'kazuto', '24,25,26')
 ,(4, '田中', '一成', 'tanaka', 'hitonari', '40,41,42,43,44,45');

-- お遍路の記録
INSERT INTO shikoku_ohenro_log (id, user_id, ohenro_id, created)
 VALUES(1, 1, 1, '2021-7-1 9:00:00')
 ,(2, 1, 2, '2021-7-1 13:00:00')
 ,(3, 1, 3, '2021-7-1 15:00:00')
 ,(4, 1, 4, '2021-7-2 9:00:00')
 ,(5, 1, 5, '2021-7-2 12:00:00')
 ,(6, 2, 81, '2021-7-3 9:00:00')
 ,(7, 2, 82, '2021-7-3 12:00:00')
 ,(8, 2, 83, '2021-7-3 17:00:00')
 ,(9, 2, 84, '2021-7-4 10:00:00')
 ,(10, 2, 85, '2021-7-4 11:00:00')
 ,(11, 2, 86, '2021-7-4 14:00:00')
 ,(12, 2, 87, '2021-7-4 16:00:00')
 ,(13, 2, 88, '2021-7-5 11:00:00')
 ,(14, 3, 24, '2021-7-6 9:00:00')
 ,(15, 3, 25, '2021-7-6 14:00:00')
 ,(16, 3, 26, '2021-7-7 11:00:00')
 ,(17, 4, 40, '2021-8-1 9:00:00')
 ,(18, 4, 41, '2021-8-1 15:00:00')
 ,(19, 4, 42, '2021-8-2 11:00:00')
 ,(20, 4, 43, '2021-8-2 16:00:00')
 ,(21, 4, 44, '2021-8-3 9:00:00')
 ,(22, 4, 45, '2021-8-3 14:00:00');

みたいなデータがあったとして、

f:id:ts0818:20220320195829p:plain

⇧「shikoku_ohenro」テーブルと「shikoku_ohenro_user」テーブルがリレーションがないんだけども、

  • shikoku_ohenro.id
  • shikoku_ohenro.name_temple
  • shikoku_ohenro_user.ohenro_ids

を一度に取得したいってことなんですが、どうもネットの情報を探しても上手い方法が見つからないというね。

で、仕方ないから、今回は、マスターのテーブルshikoku_ohenroのデータが少ないということもあり、「CROSS JOIN」を使う方法でいくことにしました。

「CROSS JOIN」については、Googleさんの「BigQuery」のドキュメントがイメージしやすいかと。

cloud.google.com

⇧すべての組み合わせで結合してくれるということですね。上記の例だと、それぞれのテーブルから1カラムずつ、3行分のデータを取ってきて「CROSS JOIN」してるので、最終的には、

 {}_3 \mathrm{ C }_1 \times {}_3 \mathrm{ C }_1 = 9行

9行分のレコードになりますと。

で、今回は、

SELECT so.id, so.name_temple, sou.ohenro_ids FROM public.shikoku_ohenro AS so
 CROSS JOIN (
  SELECT ohenro_ids
   FROM public.shikoku_ohenro_user
   WHERE id = 1
) sou;    

⇧ 上記のようなSQLを実施。

そうすると、

f:id:ts0818:20220320201357p:plain

f:id:ts0818:20220320201434p:plain

「shikoku_ohenro」テーブルの全行(取り出してるカラムは2つ)に対する、「shikoku_ohenro_user」テーブルのid=1の行(取り出してるカラムは「ohenro_ids」の1つ)の直積が得られます。

で、「JPAJava Persistence API)」で実現するには、「Native Query」を利用すれば通常のSQL文を実施できるということで「Native Query」を利用していくことに。

ただ、「JPAJava Persistence API)」について、

hiranasu.hatenadiary.org

⇧ パフォーマンスの観点からは、「JPQL(Java Persistence Query Language)」を使えるなら使った方が良いらしい。

と言うか、

qiita.com

Eclipse以外のIDEではプラグインで動くHibenate-Toolsが使えない。
Gradleのプラグインにいい感じのものがなさそうなので、gradleのTask化した。

Gradleを使ってjpaのエンティティを自動生成 - Qiita

⇧ 上記サイト様によると、GradleのタスクでJPAのエンティティの自動生成できるそうな。

stackoverflow.com

⇧ stackoverflowによると、「Hibernate」のドキュメントに記載があるそうな、う~ん、Google検索で上位表示されてないだけなのか?

話が脱線してすみません。で、今回利用するプロジェクトは、

ts0818.hatenablog.com

⇧ 前回の記事で作成したものを利用します。

「dao」プロジェクトにファイルの追加とbuild.gradleへ追記。

f:id:ts0818:20220320225830p:plain

利用するデータベースやデータベースのポート番号などデータベース接続情報は、ご自身の環境に合わせてください。

■/dao/db/hibernate.properties

# PostgreSQL
hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost:5434/test
hibernate.connection.username = postgres
hibernate.connection.password = postgres
hibernate.default_schema = public
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
hibernate.show_sql=true

■/dao/db/hibernate.reverse.engineering.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering
    SYSTEM "http://hibernate.org/dtd/hibernate-reverse-engineering-3.0.dtd">

<hibernate-reverse-engineering>
    <schema-selection match-catalog="test" match-schema=".*" match-table="area" />
    <schema-selection match-catalog="test" match-schema=".*" match-table="area_kbn" />
    <schema-selection match-catalog="test" match-schema=".*" match-table="prefectures" />
    <schema-selection match-catalog="test" match-schema=".*" match-table="shikoku_ohenro" />
    <schema-selection match-catalog="test" match-schema=".*" match-table="shikoku_ohenro_user" />
    <schema-selection match-catalog="test" match-schema=".*" match-table="shikoku_ohenro_log" />
</hibernate-reverse-engineering> 

■/dao/build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.4'
	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()
}

configurations {
	reverseMap
}

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

tasks.named('test') {
	useJUnitPlatform()
}

task hbm2java {
	group = 'auto generate entity'
	description = 'task is auto generate jpa entity'
	def basePackage = "com.example.demo.model.entity"
	def resourcesDir="$projectDir/src/main/resources"
	def srcDir="$projectDir/src/main/java"
	def preparedJdbcConfiguration = [
			//propertyfile:       resourcesDir+"/db/hibernate.properties",
			//revengfile:         resourcesDir+"/db/hibernate.reverse.engineering.xml",
			propertyfile:       "$projectDir/db/hibernate.properties",
			revengfile:          "$projectDir/db/hibernate.reverse.engineering.xml",
			packagename:        basePackage
	]

	doLast {
		project.ant {
			taskdef(
					name: "hibernatetool",
					classname: "org.hibernate.tool.ant.HibernateToolTask",
					classpath: configurations.reverseMap.asPath
			)
			hibernatetool(destdir: srcDir) {
				jdbcconfiguration(preparedJdbcConfiguration)
				hbm2java(jdk5: true, ejb3: true)
			}
		}
	}
}

⇧ で、保存し、「Gradle」>「Gradleプロジェクトのリフレッシュ」を選択。

f:id:ts0818:20220320230329p:plain

「Gradle タスク」タブでリフレッシュ。

f:id:ts0818:20220320230526p:plain

で、build.gradleに追加したタスクが表示されるので、ダブルクリックで実行。includFlatしてる影響なのか、「dao」プロジェクトのほうのGradleタスクでの実行ができないのがモヤモヤしてます...教えて偉い人。

f:id:ts0818:20220320230655p:plain

f:id:ts0818:20220320230753p:plain

Entityクラスが生成されていればOK。

f:id:ts0818:20220320230825p:plain

では、Repositoryクラスなどを作成で。

■/dao/src/main/java/com/example/demo/model/dto/ShikokuOhenroProgressDto.java

package com.example.demo.model.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ShikokuOhenroProgressDto {
  private Long id;
  private String nameTemple;
  private boolean isOhenroId;
}    

■/dao/src/main/java/com/example/demo/repository/custom/CustomeShikokuOhenroUser.java

package com.example.demo.repository.custom;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import com.example.demo.model.dto.ShikokuOhenroProgressDto;

@Repository
public class CustomeShikokuOhenroUser {

	@Autowired
	private EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public List<ShikokuOhenroProgressDto> findAllShikokuOhenroProgress (Long userId) {
		Query query = null;
		StringBuilder sb = new StringBuilder();
		sb.append("SELECT")
		  .append(" so.id")
		  .append(", so.name_temple");
		if (ObjectUtils.isEmpty(userId)) {
			sb.append(" FROM shikoku_ohenro AS so");
			query = entityManager.createNativeQuery(sb.toString());
		} else {
			sb.append(",sou.ohenro_ids")
			  .append(" FROM shikoku_ohenro AS so")
			  .append(" CROSS JOIN (")
			  .append(" SELECT")
			  .append(" ohenro_ids")
			  .append(" FROM shikoku_ohenro_user")
			  .append(" WHERE id =?1")
			  .append(") sou");

			query = entityManager.createNativeQuery(sb.toString());
			query.setParameter(1, userId);
		}
		List<Object[]> resultObject = query.getResultList();
		return convertObject(resultObject);
	}

	private List<ShikokuOhenroProgressDto> convertObject(List<Object[]> resultList) {
		List<ShikokuOhenroProgressDto> resultDtos = new ArrayList<>();
		if (CollectionUtils.isEmpty(resultList)) {
			return null;
		} else {

			Long currentId = null;
			for (Object[] obj: resultList) {
				int index = 0;
				ShikokuOhenroProgressDto shikokuOhenroProgressDto = new ShikokuOhenroProgressDto();
				currentId = Long.valueOf(String.valueOf(obj[index]));
				shikokuOhenroProgressDto.setId(currentId); index++;
				shikokuOhenroProgressDto.setNameTemple(String.valueOf(obj[index])); index++;
				if (obj.length >2 && !ObjectUtils.isEmpty(obj[index])) {
					for (Long id: Arrays.stream( String.valueOf(obj[index]).split(","))
							.map(Long::valueOf)
							.collect(Collectors.toList())) {
						if (Objects.nonNull(id) && id.equals(currentId)) {
							shikokuOhenroProgressDto.setOhenroId(true);
							break;
						} else {
							shikokuOhenroProgressDto.setOhenroId(false);
						}
					}
				} else {
					shikokuOhenroProgressDto.setOhenroId(false);
				}
				resultDtos.add(shikokuOhenroProgressDto);
			}
		}
		return resultDtos;
	}
}

■/api/src/main/java/com/example/demo/service/user/UserService.java

package com.example.demo.service.user;

import java.util.List;

import com.example.demo.model.dto.ShikokuOhenroProgressDto;
import com.example.demo.model.entity.User;

public interface UserService {
	public List<User> findAllUser ();
	List<ShikokuOhenroProgressDto> findAllShikokuOhenroIds(Long userId);
}    

■/api/src/main/java/com/example/demo/service/user/UserServiceImpl.java

package com.example.demo.service.user;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.model.dto.ShikokuOhenroProgressDto;
import com.example.demo.model.entity.User;
import com.example.demo.repository.UserRepository;
import com.example.demo.repository.custom.CustomeShikokuOhenroUser;

@Service
public class UserServiceImpl implements UserService {
	@Autowired
    private UserRepository userRepository;

	@Autowired
	private CustomeShikokuOhenroUser customeShikokuOhenroUser;

	@Override
	public List<User> findAllUser () {
	  List<User> userList = this.userRepository.findAll();
	  return userList;
	}

	@Override
	public List<ShikokuOhenroProgressDto> findAllShikokuOhenroIds(Long userId) {
		List<ShikokuOhenroProgressDto> ShikokuOhenroProgressDtos = this.customeShikokuOhenroUser.findAllShikokuOhenroProgress(userId);
		return ShikokuOhenroProgressDtos;
	}
}

■/api/src/main/java/com/example/demo/controller/UserController.java

package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.model.dto.ShikokuOhenroProgressDto;
import com.example.demo.model.entity.User;
import com.example.demo.service.user.UserService;

@RestController
@RequestMapping(value="/user")
public class UserController {

	@Autowired
	private UserService userService;

	@GetMapping
	public String hello() {
		return "Hello World";
	}

	@GetMapping(value="/all")
	public List<User> getAllUser () {
		return this.userService.findAllUser();
	}

	@GetMapping(value="/ohenro-progress/{userId}")
	public List<ShikokuOhenroProgressDto> findAllShikokuOhenroIds (@PathVariable(name="userId", required = false) Long userId) {
		return this.userService.findAllShikokuOhenroIds(userId);
	}
}

で、「api」プロジェクトを実行。

f:id:ts0818:20220321123540p:plain

で、ブラウザからアクセス。今回は、sikoku_ohenro_userテーブルのidが1のユーザーの場合で確認。

f:id:ts0818:20220321123836p:plain

リレーションが無いテーブル同士のデータを取得できました。

HTTPのGETメソッドのリクエストでnull値を送る方法が分からんので、idが無い場合ってほうの確認ができんのだけどね...

ControllerクラスのメソッドをPostMappingとかにするしかないかな...

と思ってたら、「%20」を指定すれば、nullを渡せました。

f:id:ts0818:20220321153009p:plain

ブラウザのURL欄における「%20」は何なのかと言うと、

www.suzukikenichi.com

簡潔に言えば、クエリストリング(クエリ文字列)に含まれるスペースには「+」または「%20」を用います。
クエリストリング以外でスペースが含まれるそのほかのURLには、「%20」を用います。

URLが空白を含むときは「スペース」と「%20」、「+」のどれを使うべきか? – 海外SEO情報ブログ

⇧ 上記サイト様によりますと、「スペース」のことだそうな。

まぁ、「Spring Framework」の「@PathVariable」を付与したメソッドにnullを渡す方法が分かり辛いわな...

と言うか、「Spring Framework」のドキュメント見てみたけど、リクエストに含まれる「スペース」がnull扱いになるなんてどこにも記載が無いんだが...いい加減にブラックボックス化するの止めて欲しいよね...

どれだけ、時間を無駄にしたことか...

そして悲報...

github.com

⇧ フロントエンド側で有名な「axios」っていうリクエストのライブラリは、「GET」メソッドでnullを送ることができないっぽい...

「POST」メソッドならnullを送れるらしいが...

と思ったら、

github.com

⇧ query stringだとnullを送れるようになったらしいけど、「Spring Framework」の「@PathVariable」に必要なのはquery stringじゃないんだな~、これが...

www.baeldung.com

なんか、フロントエンド側とサーバーサイド側で嚙み合ってないことも多そうですな...

Spring Framework」で「@RequestParam」を使っとけということなんかな?

ちなみに、「JPAJava Persistence API)」で用意されてる関数で、文字列に関するものを確認してみたところ、

Javaの標準APIにある、String.splitみたいなメソッドがそもそも存在しないので、取得してきたカラムの値(今回だとカンマ区切りの文字列)を、リストにするみたいなことは「JPQL(Java Persistence Query Lanuage)」だと実現できないっぽい...

う~ん、いろいろ微妙...そして徒労感が半端ない...

あと、

ohenro-online.com

⇧ お遍路の費用が想像以上にかかるってことを知りましたかね、考えてみれば、全行程で約1200kmらしいので、フルマラソンが42.195kmということは、

 1200km \div 42.195km = 28.43938855314611回

ということで、フルマラソンおよそ29回した距離を歩くってことなんで、時間がかかるし、宿泊費なんかも考えれば、仮に50日間費やすとすると、1日1万円で抑えたとしても50万円!

余程、お金に余裕が無いとできない、神々の遊びと言ったところでしょうか...

毎度モヤモヤ感が半端ない...

今回はこのへんで。