読者です 読者をやめる 読者になる 読者になる

JPQLのfetch その2

JPA EclipseLink

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