为什么 Hibernate 在使用 @Fetch 时执行多个 SELECT 查询而不是一个查询(FetchMode.JOIN)

2022-09-01 18:42:35

我有以下查询,我希望在单个选择请求中运行:

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

问题在于所有内容都是由单独的多个查询获取的。我希望在单个请求中仅获取团队和团队的玩家以及每个玩家的技能。但相反,我有多个选择查询来获取每个团队,球员,每个球员的统计数据和技能。

以下是与给定的注释一起使用的实体:

游戏实体:

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:这里是postgresql日志(其中的一部分),纯sql查询:http://pastebin.com/Fbsvmep6

为简单起见,在此问题中更改了表格的原始名称,Game是GamelistLeague,Team是TeamInfo,并且有BattterStats和PitcherStats而不是一个PlayerStats。

日志中的第一个查询是上面这个问题(命名查询)中所示的查询,如果我直接在数据库中执行它,则会根据需要返回所有内容。


答案 1

您遇到了一个众所周知的问题,即“N+1 选择”。简而言之,当您选择父实体并且休眠将为与具有 OneToOne 的父实体相关的子实体进行其他选择时,会出现“N+1 选择”问题。因此,如果数据库中有“N”个父子记录,则休眠将获取所有父项,选择一个,然后获取每个子项的单独选择,总共选择 N+1 个。
对于休眠中的“N + 1”问题有两种方法:
1.“加入获取”所有一对一儿童。
启用第二级缓存,并在一对一子@Cache上使用注释。

你的问题是你没有“加入”所有OneToOne孩子。您必须“联接”提取它们,包括可传递子项(从子项本身引用或在集合中引用的实体)。

使OneToOne变得懒惰(因为它默认是急切的)只是部分解决方案,因为休眠只有在您访问子项上的一些getter时才会为子项进行选择,但从长远来看,它仍然会使所有N个选择。


答案 2

辅助查询来自:

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

因此,您需要:

  1. 使所有关联都成为懒惰。默认情况下,所有@ManyToOne和@OneToOne关联都是 EAGER 的,因此最好让它们 LAZY 并且只在查询的基础上覆盖 fetch 计划。

  2. 删除 ,它实质上是一个 EAGER fetch 指令。在你的例子中,不仅team2属性被获取,而且它的玩家和技能也被获取。@Fetch(FetchMode.JOIN)


推荐