web-dev-qa-db-ja.com

Hibernateは1つではなく複数の選択要求を実行しています(結合フェッチを使用)

単一の選択要求で実行されると予想される次のクエリがあります。

@NamedQuery(name=Game.GET_GAME_BY_ID1,
                query = "SELECT g FROM Game g " +
                        "JOIN FETCH g.team1 t1 " +
                        "JOIN FETCH t1.players p1 " +
                        "JOIN FETCH p1.playerSkill skill1 " +
                        "where g.id=:id")

問題は、すべてが個別の複数のクエリによってフェッチされることです。チームとチームのプレイヤーと各プレイヤーのスキルのみを1回のリクエストで取得する必要があります。しかし、代わりに、各チーム、プレーヤー、各プレーヤーの統計とスキルを取得するための複数の選択クエリがあります。

以下は、指定された注釈で使用されるエンティティです。

ゲームエンティティ:

public class Game implements Serializable {
    private Integer id;
    private Integer dayNumber;
    private Long date;
    private Integer score1;
    private Integer score2;

    private Team team1;
    private Team team2;

    ....

    @ManyToOne(fetch=FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    @JoinColumn(name="team_id1")
    public Team getTeam1() {
        return team1;
    }


    public void setTeam1(Team team1) {
        this.team1 = team1;
    }

    // uni directional many to one association to Team
    @ManyToOne(fetch=FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    @JoinColumn(name="team_id2")
    public Team getTeam2() {
        return team2;
    }


    public void setTeam2(Team team2) {
        this.team2 = team2;
    }
}

チームエンティティ:

public class Team implements Serializable {
    ...
    private Set<Player> players;
    ...
    @OneToMany(mappedBy="team", targetEntity=Player.class, fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @Fetch(FetchMode.JOIN)
    @OrderBy(value="batOrder, pitRotationNumber ASC")
    public Set<Player> getPlayers() {
        return players;
    }


    public void setPlayers(Set<Player> players) {
        this.players = players;
    }
}

プレイヤーエンティティ:

public class Player implements Serializable {
    private PlayerStat playerStats;
    private PlayerSkill playerSkill;
    ...
    @OneToOne(mappedBy="player", cascade=CascadeType.ALL)
    @Fetch(FetchMode.JOIN)
    public PlayerStat getPlayerStats() {
        return this.playerStats;
    }

    public void setPlayerStats(PlayerStat playerStats) {
        this.PlayerStats = playerStats;
    }

    ...

    @OneToOne(mappedBy="player", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @Fetch(FetchMode.JOIN)
    public PlayerSkill getPlayerSkill() {
        return this.playerSkill;
    }

    public void setPlayerSkill(PlayerSkill playerSkill) {
        this.playerSkill = playerSkill;
    }
}

ミスを指摘していただけますか?ゲーム、チーム、チームのプレイヤー、各プレイヤーのスキルを読み込むために1つの選択クエリが必要です。

EDIT 1:ここはpostgresqlログ(その一部)、純粋なSQLクエリです: http://Pastebin.com/Fbsvmep6

この質問では、簡単にするためにテーブルの元の名前が変更されています。ゲームはGamelistLeague、チームはTeamInfo、そしてPlayerStatの代わりにBatterStatsとPitcherStatsがあります

ログからの最初のクエリは、上記のこの質問に示されているクエリ(名前付きクエリ)です。データベースで直接実行すると、必要に応じてすべてが返されます。

13
maximus

「N + 1選択」というよく知られた問題が発生しています。つまり、親エンティティを選択すると、「N + 1選択」問題が発生し、HibernateはOneToOneを使用して親に関連する子を追加選択します。したがって、データベースに「N」個の親子レコードがある場合、hibernateはすべての親を1回の選択で取得し、次に各子を個別の選択で取得して、合計N + 1回の選択を行います。
Hibernateの「N + 1」問題には2つのアプローチがあります。
1。 「Join Fetch」all OneToOneの子。
2。 2次キャッシュを有効にし、OneToOneの子で@Cacheアノテーションを使用します。

問題は、すべてのOneToOne子を「結合フェッチ」しなかったことです。推移的な子(子自体またはコレクション内から参照されるエンティティ)を含め、それらすべてを「結合フェッチ」する必要があります。

OneToOneをレイジーにする(デフォルトでは熱心なため)のは部分的な解決策にすぎません。なぜなら、hibernateは子のゲッターにアクセスするときにのみ子を選択しますが、長期的にはN個すべてを選択するからです。

28
outdev

二次クエリは以下から来ています。

_@ManyToOne(fetch=FetchType.EAGER)
@Fetch(FetchMode.JOIN)
@JoinColumn(name="team_id2")
public Team getTeam2() {
    return team2;
}
_

だから、あなたはする必要があります:

  1. すべての関連付けをLAZYにします。デフォルトでは、 すべての@ManyToOneおよび@OneToOneの関連付け はEAGERであるため、LAZYにしてクエリベースでのみフェッチプランをオーバーライドする方が適切です。

  2. 本質的にEAGERフェッチディレクティブである@Fetch(FetchMode.JOIN)を削除します。あなたの場合、team2プロパティだけでなく、そのプレイヤーとスキルも取得されます。

5
Vlad Mihalcea