🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Thymeleaf

Thymeleaf(タイムリーフ)とは、Java用のテンプレートエンジンで、特定のフレームワークに依存せず使用することが可能です。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

2回答

3871閲覧

SpringBoot + JPA + Thymeleaf

icezoom

総合スコア36

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Thymeleaf

Thymeleaf(タイムリーフ)とは、Java用のテンプレートエンジンで、特定のフレームワークに依存せず使用することが可能です。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

0クリップ

投稿2019/09/22 03:16

編集2019/11/06 16:04

自習用で作っているものです。
リポジトリクラスで、結合したテーブルから取得した結果を最終的にTyhmeleafに渡して画面に表示したいです。

@Data @Entity public class User { @Id @GeneratedValue private Long id; private String userName; private String mailAddr; private Long groupId; }

Java

1 2@Data 3@Entity 4public class UserGroup { 5 6 @Id 7 private Long groupId; 8 9 private String groupName; 10}

上記のテーブルをrepositoryで外部結合

Java

1@Repository 2public interface UserRepository extends JpaRepository<User, Long> { 3 4 @Query(value = "SELECT d.id,d.user_name,_no,d.mail_addr,g.group_name,null AS group_id " 5 + "FROM user u " 6 + "LEFT JOIN group g ON d.group_id = g.group_id",nativeQuery = true) 7 public List<User> findUser(); 8} 9

Java(service)

1public List<User> findUserGroup() { 2 return UserRepository.findUserGroup(); 3 } 4

java(controller)

1@Controller 2@RequestMapping("/") 3public class IndexController { 4 @Autowired 5 private IndexService service; 6 @RequestMapping(method = RequestMethod.GET) 7 public String index(Model model) { 8 model.addAttribute("UserGroup",service.findUserGroup()); 9 return "index"; 10 }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/09/22 03:28

ASP.NET MVC Framework にも .NET Framework にも関係ない Java の話のように思えますが、そうであれば付与したタグは不適切です。見直してください。
icezoom

2019/09/22 04:54

申し訳ありません。修正します。
q_sane_q

2019/09/22 05:28

Controllerにノイズが多いようですが…… **は太字にする予定だった? service.findTelGroupList()は生きている処理ですか?
icezoom

2019/09/22 05:48

**model.addAttribute("telDataList",service.findTelDataGroup());** は太文字にして、処理が正しいかを強調するつもりだったのですが… 初心者でうまく質問ができていなくて申し訳ありません
q_sane_q

2019/09/22 05:55

とりあえず、生きている(今回の問題に関係のある)処理だけにしていただけると嬉しいです。
icezoom

2019/09/22 06:08

処理内容確認しました。 model.addAttribute("telDataGroupList",service.findTelDataGroupList());をデバッグで確認したところ、 「groupName」のデータ取得できていないと思われます。 結合の仕方が誤っているのでしょうか…
guest

回答2

0

ベストアンサー

JPAではこのような場合、エンティティクラスに関連(リレーション)を持たせます。
以下にtel_groupテーブルが親、tel_dataテーブルが子という親子関係を持っているという前提で実装方法を説明いたします。

なお、エンティティクラスに関連(リレーション)を持たせたくないということであれば別の方法で解決する必要がありますのでこの回答はスルーしてください。

スキーマ

以下の内容のテーブルおよびテストデータで動作検証を行いました。以降の説明はこれらのスキーマを前提に行います。

sql

1CREATE TABLE tel_group ( 2 id BIGINT AUTO_INCREMENT, 3 group_name VARCHAR(80) NOT NULL, 4 PRIMARY KEY(id) 5); 6 7CREATE TABLE tel_data ( 8 id BIGINT AUTO_INCREMENT, 9 user_name VARCHAR(80) NOT NULL, 10 tel_no VARCHAR(20) NOT NULL, 11 mail_addr VARCHAR(60) NOT NULL, 12 group_id BIGINT NOT NULL, 13 PRIMARY KEY(id) 14); 15 16ALTER TABLE tel_data ADD CONSTRAINT FOREIGN KEY FKEY_tel_data_group_id_group_id (group_id) REFERENCES tel_group (id);

テストデータ

検証用データです。

sql

1insert into tel_group (id, group_name) values (1, 'GROUP A'); 2insert into tel_group (id, group_name) values (2, 'GROUP B'); 3insert into tel_group (id, group_name) values (3, 'GROUP C'); 4insert into tel_group (id, group_name) values (4, 'GROUP D'); 5insert into tel_group (id, group_name) values (5, 'GROUP E'); 6 7insert into tel_data (id, user_name, tel_no, mail_addr, group_id) values (1, 'USER 1', '999-999-999', 'xxx.xxx@xxx.xxx', 1); 8insert into tel_data (id, user_name, tel_no, mail_addr, group_id) values (2, 'USER 2', '999-999-999', 'xxx.xxx@xxx.xxx', 1); 9insert into tel_data (id, user_name, tel_no, mail_addr, group_id) values (3, 'USER 3', '999-999-999', 'xxx.xxx@xxx.xxx', 2); 10insert into tel_data (id, user_name, tel_no, mail_addr, group_id) values (4, 'USER 4', '999-999-999', 'xxx.xxx@xxx.xxx', 4); 11insert into tel_data (id, user_name, tel_no, mail_addr, group_id) values (5, 'USER 5', '999-999-999', 'xxx.xxx@xxx.xxx', 4); 12insert into tel_data (id, user_name, tel_no, mail_addr, group_id) values (6, 'USER 6', '999-999-999', 'xxx.xxx@xxx.xxx', 5);

sql

1SELECT d.id, d.user_name, d.tel_no, d.mail_addr, d.group_id, g.group_name FROM tel_data d LEFT JOIN tel_group g ON d.group_id = g.id 2 3+----+-----------+-------------+-----------------+----------+------------+ 4| id | user_name | tel_no | mail_addr | group_id | group_name | 5+----+-----------+-------------+-----------------+----------+------------+ 6| 1 | USER 1 | 999-999-999 | xxx.xxx@xxx.xxx | 1 | GROUP A | 7| 2 | USER 2 | 999-999-999 | xxx.xxx@xxx.xxx | 1 | GROUP A | 8| 3 | USER 3 | 999-999-999 | xxx.xxx@xxx.xxx | 2 | GROUP B | 9| 4 | USER 4 | 999-999-999 | xxx.xxx@xxx.xxx | 4 | GROUP D | 10| 5 | USER 5 | 999-999-999 | xxx.xxx@xxx.xxx | 4 | GROUP D | 11| 6 | USER 6 | 999-999-999 | xxx.xxx@xxx.xxx | 5 | GROUP E | 12+----+-----------+-------------+-----------------+----------+------------+ 136 rows in set (0.00 sec)

JPA

エンティティクラスの実装

TelGroupクラスとTelDataクラスに関連を持たせる場合、外部キー(group_id)を持つTelDataエンティティクラスにOneToOneアノテーションを付与して単方向のリレーションを宣言します。
また外部キーのgroup_idをそのままフィールドとして定義するのではなく、代わりにTelGroupエンティティをフィールドとして定義します。

ちなみに単方向のリレーションとは、TelDataクラス側からTelGroupクラスを参照できる(見つけることができる)が、TelGroupクラスからTelDataクラスは参照できない(見つけることができない)という意味です。

要件によっては双方向の関連を持たせたい場合もあると思いますが、その場合エンティティクラスの実装を変える必要が出てきます。

※以下のコードではColumnアノテーションを使っていますが必須ではありません。

java

1import lombok.Data; 2 3import javax.persistence.Column; 4import javax.persistence.Entity; 5import javax.persistence.GeneratedValue; 6import javax.persistence.GenerationType; 7import javax.persistence.Id; 8import javax.persistence.Table; 9 10@Entity 11@Table(name = "tel_group") 12@Data 13public class TelGroup { 14 @Id 15 @GeneratedValue(strategy = GenerationType.IDENTITY) 16 private Long id; 17 @Column(name = "group_name", length = 80, nullable = false) 18 private String groupName; 19}

java

1import lombok.Data; 2 3import javax.persistence.Column; 4import javax.persistence.Entity; 5import javax.persistence.GeneratedValue; 6import javax.persistence.GenerationType; 7import javax.persistence.Id; 8import javax.persistence.JoinColumn; 9import javax.persistence.OneToOne; 10import javax.persistence.Table; 11 12@Entity 13@Table(name = "tel_data") 14@Data 15public class TelData { 16 @Id 17 @GeneratedValue(strategy = GenerationType.IDENTITY) 18 private Long id; 19 @Column(name = "user_name", length = 80, nullable = false) 20 private String userName; 21 @Column(name = "tel_no", length = 20, nullable = false) 22 private String telNo; 23 @Column(name = "mail_addr", length = 60, nullable = false) 24 private String mailAddr; 25 @OneToOne 26 @JoinColumn(name = "group_id") 27 private TelGroup telGroup; 28}

リポジトリの実装

エンティティが関連を持っているのでデータを全件取得するだけであればJpaRepositoryに定義されているfindAllメソッドで済みます。なのでネイティブSQLを使った新しいメソッドの定義は不要です。

java

1import com.example.demo.model.TelData; 2import org.springframework.data.jpa.repository.JpaRepository; 3 4public interface TelDataRepository extends JpaRepository<TelData, Long> { 5}

テンプレート

コントローラで以下のように取得したデータをモデルにセットした場合

java

1List<TelData> telDataList = telDataRepository.findAll(); 2model.addAttribute("telDataList", telDataList);

テンプレート内では以下のようにモデルにアクセスできます。
たとえば、TelGroupのgroupNameフィールドを取得したい場合
JavaコードではtelData.getTelGroup().getGroupName()のように記述できるので、テンプレートでも同じ要領で telData.telGroup.groupName と記述します。

html

1<table> 2 <thead> 3 <tr> 4 <th>ユーザID</th> 5 <th>ユーザ名</th> 6 <th>tel no</th> 7 <th>mail address</th> 8 <th>グループID</th> 9 <th>グループ名</th> 10  </tr> 11 </thead> 12 <tbody> 13 <tr th:each="telData : ${telDataList}" > 14 <td th:text="${telData.id}"></td> 15 <td th:text="${telData.userName}"></td> 16 <td th:text="${telData.telNo}"></td> 17 <td th:text="${telData.mailAddr}"></td> 18 <td th:text="${telData.telGroup.id}"></td> 19 <td th:text="${telData.telGroup.groupName}"></td> 20 </tr> 21 </tbody> 22</table>

この方法の注意点

この実装では扱うデータ件数によっては、いわゆるN+1問題という性能に影響を与える問題が発生することがありますのでご注意ください。

この例のテストデータを使ってデータ全件を検索した場合、実行されるSQLは(1)が1回、(2)が4回の計5回になります。
これは最初にtel_dataテーブルから全件検索し、次にtel_data.group_idで別途tel_groupテーブルを検索するという動きになります。

(1)

sql

1 select 2 teldata0_.id as id1_0_, 3 teldata0_.mail_addr as mail_add2_0_, 4 teldata0_.group_id as group_id5_0_, 5 teldata0_.tel_no as tel_no3_0_, 6 teldata0_.user_name as user_nam4_0_ 7 from 8 tel_data teldata0_

(2)

sql

1 select 2 telgroup0_.id as id1_1_0_, 3 telgroup0_.group_name as group_na2_1_0_ 4 from 5 tel_group telgroup0_ 6 where 7 telgroup0_.id=?

この問題の対処方法

N+1問題を避けるにはTelDataRepositoryを以下のように改修します。
Queryアノテーションに記述している文字列はSQLのように見えますが正確にはJPQLといいます。
※findAllメソッドをオーバーライドしていますが、別の名前を付けたカスタムメソッドにしても構いません。

java

1import com.example.demo.model.TelData; 2import org.springframework.data.jpa.repository.JpaRepository; 3import org.springframework.data.jpa.repository.Query; 4 5import java.util.List; 6 7public interface TelDataRepository extends JpaRepository<TelData, Long> { 8 @Query("SELECT d FROM TelData d JOIN FETCH d.telGroup") 9 @Override 10 List<TelData> findAll(); 11}

このfindAllメソッドを実行すると、下記のSQLが1回だけ実行されます。

sql

1select 2 teldata0_.id as id1_0_0_, 3 telgroup1_.id as id1_1_1_, 4 teldata0_.mail_addr as mail_add2_0_0_, 5 teldata0_.group_id as group_id5_0_0_, 6 teldata0_.tel_no as tel_no3_0_0_, 7 teldata0_.user_name as user_nam4_0_0_, 8 telgroup1_.group_name as group_na2_1_1_ 9 from 10 tel_data teldata0_ 11 inner join 12 tel_group telgroup1_ 13 on teldata0_.group_id=telgroup1_.id

SQLのログを出力する

JPAがどのようなSQLを組み立てて実行するかは、設定ファイルでSQLのログを出力するように設定すると分かります。
以下は必要な設定項目(yml形式)の抜粋です。設定をapplication.properteisファイルで管理している場合は適宜読み替えてください。

spring: jpa: show-sql: true properties: hibernate: format_sql: true logging: level: org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace

投稿2019/09/22 14:58

rubytomato

総合スコア1752

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

icezoom

2019/09/23 15:17

本当にわかりやすく説明して頂きありがとうございます! リレーションの理解があまりできていませんでした。 ご指摘のように実装した結果うまくgroupNameを取得できました! JPQLで結合した新しい結合データは、別のメソッドを用意して呼び出す必要があると思っていました。
guest

0

これHTMLで使用しているのは
model.addAttribute("telDataGroupList",service.findTelDataGroup());
だけですよね?

そしたら検索で返ってきてるのはTelDataクラスで、そこにはgroupNameというフィールドは存在しないので受け取りできていないと思います
単純に取得するだけならTelDataクラスにgroupNameを作れば動くのではないでしょうか?

ただTelDataテーブルにはgroupNameという列はないので、それを示すアノテーション@Transientをつけないといけなかったと思います

クエリを柔軟にできるので自分がよくやるやり方(コメントの3)は↓の感じで(ddl-auto: create はいつも付けてない)

Java

1// テーブルに対応したEntity 2@Data 3@Entity 4public class TelData { 5 6 @Id 7 @GeneratedValue 8 private Long id; 9 private String userName; 10 private String telNo; 11 private String mailAddr; 12 private Long groupId; 13}
// JOINなどをした検索結果を受け付けるEntity(名前は何でも) @Data @Entity public class TelData2 { @Id private Long id; private String userName; private String telNo; private String mailAddr; private Long groupId; private String groupName; }

Java

1@Repository 2public interface TelDataRepository extends JpaRepository<TelData, Long>, TelDataRepositoryCustom { 3}

Java

1// カスタムクエリ用Repositoryインタフェース 名前はリポジトリインタフェース名 + Custom 2public interface TelDataRepositoryCustom{ 3 public List<TelData2> findTelDataGroup(); 4}

Java

1// カスタムクエリ用Repositoryクラス 名前はリポジトリインタフェース名 + Impl 2 3import java.util.List; 4import javax.persistence.EntityManager; 5import javax.persistence.Query; 6import org.springframework.beans.factory.annotation.Autowired; 7 8public interface TelDataRepositoryImpl implements TelDataRepositoryCustom { 9 10 @Autowired EntityManager entityManager; 11 12 public List<TelData2> findTelDataGroup(){ 13 14 String sql = "SELECT d.id,d.user_name,d.tel_no,d.mail_addr,g.group_name,null AS group_id " 15 + "FROM tel_data d " 16 + "LEFT JOIN tel_group g ON d.group_id = g.group_id"; 17 18 Query query = entityManager.createNativeQuery(sql, TelData2.class); 19 List<TelData2> list = (List<TelData2>)query.getResultList(); 20 return list; 21 // キャストの警告が出るので気になったら@SuppressWarningsアノテーションで消す 22 } 23}

で、

Java

1// telDataRepository はTelDataRepository(TelDataRepositoryCustomやTelDataRepositoryImplではない) 2telDataRepository.findTelDataGroup();

を使って呼び出した結果を画面に返して使用する。
インタフェースやクラスが増えることとやや複雑になるデメリットはありますが、
クエリ・結果を比較的自由に弄れるので個人的にはよくやっています。

投稿2019/09/22 06:33

編集2019/09/22 14:13
q_sane_q

総合スコア610

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

icezoom

2019/09/22 06:46

回答ありがとうございます。 結合データの取得でしようしているのは、 model.addAttribute("telDataGroupList",service.findTelDataGroup());  だけです。 やはりTelDataクラスにgroupNameを作れば、動くということなんですね。 entityのTelDataクラスgroupNameを作成すれば、TelDataテーブルに列が追加されます。 その場合、@Transientは必要なのでしょうか。
icezoom

2019/09/22 07:59

TelDataにgroupNameを作成し、実行しましたが、常にgroupNameがnullで取得されます。。。
q_sane_q

2019/09/22 08:17

長くなるのでこっちに書くと読みにくいと思いましたが、一連の話なのでこっちに書いた方がよかったですね、以下に書き直します すいませんちょっと言葉足らずでした。 TelDataテーブルに列が追加される、ということは設定ファイル(application.ymlかapplication.properties)でddl-auto: create を設定している……ということでいいですよね? @Transientをつけたフィールドは永続化で無視されるので、テーブルに列が作成されることはないと思います。 ただ検索結果の取得もできなくなるので、 1. @TransientをつけてgroupNameフィールドを作成、TelDataを検索してListを取得した後でgroupNameを別検索して突っ込む 2. @ManyToOne、@OneToManyでEntityに結合関係を示す 3. テーブルに直接紐づかないEntityクラスを作り、カスタムクエリで取得する あたりでしょうか…… 私は3.の方法をよくやりますが、クラスやインタフェースが増えることになるので
icezoom

2019/09/22 13:50 編集

丁寧にありがとうございます。 やり方としては、上記の1~3のどれか1つを行うことで実装が可能ということですよね? 一度どの方法がよいのか試してみます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問