web-dev-qa-db-fra.com

Hibernate effectue plusieurs requêtes de sélection au lieu d'une (en utilisant la récupération de jointure)

J'ai la requête suivante que je m'attends à exécuter dans une seule demande de sélection:

@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")

Le problème est que tout est récupéré par plusieurs requêtes distinctes. Je souhaite que seuls les joueurs de l'équipe et de l'équipe et les compétences de chaque joueur soient récupérés en une seule demande. Mais à la place, j'ai plusieurs requêtes sélectionnées pour récupérer chaque équipe, chaque joueur, les statistiques et les compétences de chaque joueur.

Voici les entités utilisées avec des annotations données:

Entité de jeu:

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;
    }
}

Entité d'équipe:

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;
    }
}

Entité du joueur:

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;
    }
}

Pourriez-vous nous signaler les erreurs commises? J'ai besoin d'une requête de sélection pour charger le jeu, ce sont les équipes, les joueurs de l'équipe et les compétences de chaque joueur.

EDIT 1: voici le journal postgresql (une partie de celui-ci), des requêtes SQL pures: http://Pastebin.com/Fbsvmep6

Les noms originaux des tables sont modifiés dans cette question pour plus de simplicité, Game est GamelistLeague, Team est TeamInfo, et il y a BatterStats et PitcherStats au lieu d'un PlayerStat

La première requête des journaux est celle montrée dans cette question ci-dessus (requête nommée) qui, si je l'exécute directement dans la base de données, retourne tout selon les besoins.

13
maximus

Vous rencontrez un problème bien connu, alias le "N + 1 sélectionne". En bref, le problème "N + 1 sélectionne" se produit lorsque vous sélectionnez une entité parent et hibernate fera une sélection supplémentaire pour un enfant lié au parent avec OneToOne. Donc, si vous avez des enregistrements parent-enfant "N" dans la base de données, hibernate obtiendra tous les parents avec une sélection, puis mettra chaque enfant dans une sélection séparée, faisant un total de N + 1 sélections.
Il existe deux approches pour le problème "N + 1" en hibernation:
1. "Join Fetch" tous Enfants OneToOne.
2. Activez le cache de deuxième niveau et utilisez l'annotation @Cache sur les enfants OneToOne.

Votre problème est que vous n'avez pas "rejoint la récupération" de tous les enfants OneToOne. Vous devez tous les "joindre chercher", y compris les enfants transitifs (entités référencées par les enfants eux-mêmes ou au sein de la collection).

Rendre OneToOne paresseux (parce qu'il est impatient par défaut) n'est qu'une solution partielle, car hibernate ne sélectionnera un enfant que lorsque vous accéderez à un getter sur l'enfant, mais à long terme, il fera toujours tous les N sélections.

28
outdev

Les requêtes secondaires proviennent de:

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

Vous devez donc:

  1. Rendez toutes les associations paresseuses. Par défaut, toutes les associations @ManyToOne et @OneToOne sont EAGER, il est donc préférable de les avoir paresseux et de remplacer uniquement le plan de récupération sur la base d'une requête.

  2. Supprimez la @Fetch(FetchMode.JOIN), qui est essentiellement une directive d'extraction EAGER. Dans votre cas, non seulement la propriété team2 est récupérée, mais aussi ses joueurs et ses compétences.

5
Vlad Mihalcea