自習用で作っているものです。
リポジトリクラスで、結合したテーブルから取得した結果を最終的に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 04:54
2019/09/22 05:28
2019/09/22 05:48
2019/09/22 05:55
2019/09/22 06:08
回答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
総合スコア1752
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総合スコア610
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/09/22 06:46
2019/09/22 07:59
2019/09/22 08:17
2019/09/22 13:50 編集
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。