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は必要に応じてちゃんと付けましょうという教訓。