JPQLのfetch その2
JOIN (≠JOIN FETCH)
JPQLはJOIN FETCHではなくJOINだけでも書ける。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN t.members m")
SELECT DISTINCT t1.ID, t1.NAME FROM member t0, team t1 WHERE (t0.teamId = t1.ID)
1
Aチーム
SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [1]
ハンニバル
フェイス
B.A.
マードック
-----
2
MP
SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [2]
リンチー
デッカー
フルブライト
-----
FETCHしていないのでN+1になる。使いドコロとしてはWHERE条件にmemberを加えたいけどFETCHはしない場合など。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN t.members m WHERE m.name LIKE '%B%'") List<Team> temas = em.createNamedQuery("Find", Team.class).getResultList(); for (Team t : teams) { System.out.println(t.getId()); System.out.println(t.getName()); System.out.println("-----"); }
SELECT t.ID, t1.NAME FROM member t0, team t1 WHERE (t0.NAME LIKE ? AND (t0.teamId = t1.ID)) bind => [%B%] 1 Aチーム -----
QueryHints.FETCH (eclipselink)
JPA上ではFETCHは子プロパティまでで、孫プロパティをJOIN FETCHしてもN+1になるが、QueryHintでEclipseLink独自ヒントQueryHints.FETCHを使うと、多段FETCHしてくれるらしい。
テーブルを増やして実験。
team
| id | name |
|---|---|
| 1 | Aチーム |
| 2 | MP |
member
| id | name | teamId | titleId |
|---|---|---|---|
| 1 | ハンニバル | 1 | 1 |
| 2 | フェイス | 1 | 2 |
| 3 | B.A. | 1 | 3 |
| 4 | マードック | 1 | 4 |
| 101 | リンチ | 2 | 1 |
| 102 | デッカー | 2 | 1 |
| 103 | フルブライト | 2 | 5 |
title
| id | name |
|---|---|
| 1 | 大佐 |
| 2 | 中尉 |
| 3 | 軍曹 |
| 4 | 大尉 |
| 5 | 准将 |
@Entity @Table(name="team") public class Team { @Id private Integer id; private String name; @OneToMany(mappedBy="team") private List<Member> members; } @Entity @Table(name="member") public class Member { @Id private Integer id; private String name; @ManyToOne @JoinColumn(name="teamId") private Team team; @ManyToOne @JoinColumn(name="titleId") private Title title; } @Entity @Table(name="title") public class Title { @Id private Integer id; private String name; @OneToMany(mappedBy="title") private List<Member> members; } ※getter, setter略
まずはhint無し。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN FETCHt.members m JOIN FETCH m.title ti") List<Team> temas = em.createNamedQuery("Find", Team.class).getResultList(); for (Team t : teams) { System.out.println(t.getId()); System.out.println(t.getName()); t.getMembers().forEach(m -> System.out.println("\t" + m.getName() + m.getTitle().getName())); System.out.println("-----"); }
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId, t0.titleId FROM member t0, team t1 WHERE (t0.teamId = t1.ID)
SELECT ID, NAME FROM title WHERE (ID = ?) bind => [4]
SELECT ID, NAME FROM title WHERE (ID = ?) bind => [2]
SELECT ID, NAME FROM title WHERE (ID = ?) bind => [1]
SELECT ID, NAME FROM title WHERE (ID = ?) bind => [3]
SELECT ID, NAME FROM title WHERE (ID = ?) bind => [5]
1
Aチーム
マードック大尉
フェイス中尉
ハンニバル大佐
B.A.軍曹
-----
2
MP
デッカー大佐
フルブライト准将
リンチ大佐
-----
孫プロパティのtitleは個別にSELECTが実行されている。
hintを付ける。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN FETCH t.members m JOIN FETCH m.title ti" , hints = {@QueryHint(name = QueryHints.FETCH, value = "t.members.title")})
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId, t0.titleId, t2.ID, t2.NAME FROM member t0, title t2, team t1 WHERE ((t0.teamId = t1.ID) AND (t2.ID = t0.titleId))
1
Aチーム
マードック大尉
フェイス中尉
ハンニバル大佐
B.A.軍曹
-----
2
MP
デッカー大佐
フルブライト准将
リンチ大佐
-----
SELECT 1回でFETCHしている。
QueryHints.FETCH の裏効果
QueryHints.FETCHを使うと、自動でDISTINCT, JOIN FETCH状態になる。
@NamedQuery(name="Find", query="SELECT t FROM Team t JOIN t.members m JOIN m.title ti" , hints = {@QueryHint(name = QueryHints.FETCH, value = "t.members.title")})
SELECT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId, t0.titleId, t2.ID, t2.NAME FROM member t0, title t2, team t1 WHERE ((t0.teamId = t1.ID) AND (t2.ID = t0.titleId))
1
Aチーム
ハンニバル大佐
フェイス中尉
B.A.軍曹
マードック大尉
-----
2
MP
リンチ大佐
デッカー大佐
フルブライト准将
-----
SQLにDISTINCTは付いていないが、グルーピングされて取得されているし、JOIN(≠JOIN FETCH)だがSELECT 1回で全部取得している。
hintがなければDISTINCTもFETCHもされない。
ハマったのはこの裏効果で、hint付きでJOIN(≠JOIN FETCH)をしていたJPQLを修正した際、孫プロパティが不要になったのでhintを外したら、FETCHもDISTINCTもされなくなったという。。。
QueryHints.FETCHの仕様が変わる可能性もあるし、DISTINCT, FETCHは必要に応じてちゃんと付けましょうという教訓。