JPQLのfetch その1

タイトルの通りだが勉強不足でハマったのでメモ。
Java8 + JPA2.1 + eclipselink2.6

テーブル

team

id name
1 Aチーム
2 MP

member

id name teamId
1 ハンニバル 1
2 フェイス 1
3 B.A. 1
4 マードック 1
101 リンチ 2
102 デッカー 2
103 フルブライト 2

Entity

@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;
}
※getter, setter略

1件取得

@NamedQuery(name="Find", query="SELECT t FROM Team t WHERE t.id = :id")

Team t = em.createNamedQuery("Find", Team.class).setParameter("id", 1).getSingleResult();
System.out.println(t.getId());
System.out.println(t.getName());
t.getMembers().forEach(m -> System.out.println("\t" + m.getName()));

実行ログ

SELECT ID, NAME FROM team WHERE (ID = ?) bind => [1]
1
Aチーム
SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [1]
    ハンニバル
    フェイス
    B.A.
    マードック

JPQLでJOINしていないのでgetMembers()の際にSQLが発行される、N+1問題というやつ。

なのでクエリでJOIN FETCHしてやると、memberもまとめて取ってくる。

@NamedQuery(name="Find", query="SELECT t FROM Team t JOIN FETCH t.members WHERE t.id = :id")
SELECT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId FROM member t0, team t1 WHERE ((t1.ID = ?) AND (t0.teamId = t1.ID)) bind => [1]
1
Aチーム
    ハンニバル
    フェイス
    B.A.
    マードック

複数件取得

@NamedQuery(name="Find", query="SELECT t FROM Team t")

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()));
    System.out.println("-----");
}
SELECT ID, NAME FROM team
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]
    リンチー
    デッカー
    フルブライト
-----

1行と同様、N+1でデータを取得してくる。

なのでJOIN FETCHをする。

@NamedQuery(name="Find", query="SELECT t FROM Team t JOIN FETCH t.members")
SELECT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId FROM member t0, team t1 WHERE (t0.teamId = t1.ID)
1
Aチーム
    ハンニバル
    フェイス
    B.A.
    マードック
-----
1
Aチーム
    ハンニバル
    フェイス
    B.A.
    マードック
-----
1
Aチーム
    ハンニバル
    フェイス
    B.A.
    マードック
-----
1
Aチーム
    ハンニバル
    フェイス
    B.A.
    マードック
-----
2
MP
    リンチー
    デッカー
    フルブライト
-----
2
MP
    リンチー
    デッカー
    フルブライト
-----
2
MP
    リンチー
    デッカー
    フルブライト
-----

なんてこったい。

ということを無くすためにDISTINCTをつける。

@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN FETCH t.members")
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId FROM member t0, team t1 WHERE (t0.teamId = t1.ID)
1
Aチーム
    ハンニバル
    フェイス
    B.A.
    マードック
-----
2
MP
    リンチー
    デッカー
    フルブライト
-----

SQLにdistinctがついた上で、オブジェクトレベルでもTeamでグループ化される。

その2へ続く