【问题标题】:Why does Hibernate execute multiple SELECT queries instead of one when using @Fetch(FetchMode.JOIN)为什么 Hibernate 在使用 @Fetch(FetchMode.JOIN) 时执行多个 SELECT 查询而不是一个
【发布时间】:2015-03-12 05:06:29
【问题描述】:

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

@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,并且有 BatterStats 和 PitcherStats 而不是一个 PlayerStat

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

【问题讨论】:

  • 能否请您发布正在执行的实际 SQL?如果关闭延迟获取会发生什么?
  • @chrylis 请查看编辑。我可以尝试关闭延迟获取,但稍后,虽然我需要将它设置为延迟以用于其他不应急切获取玩家的查询。
  • Hibernate...自 2001 年以来让聪明的人执行愚蠢的查询。可能是时候放弃 Hibernate 膨胀软件并切换到 something closer to the metal :)
  • @LanceJava 哈哈哈:D
  • 我只是想对 Lance Java 说点什么。我打算放弃休眠并使用mybatis。天哪,我在冬眠,真傻。

标签: java hibernate jpa hql fetch


【解决方案1】:

次要查询来自:

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

所以,你需要:

  1. 将所有关联设为 LAZY。默认情况下,all @ManyToOne and @OneToOne associations 是 EAGER,因此最好让它们 LAZY 并且仅基于查询覆盖获取计划。

  2. 删除@Fetch(FetchMode.JOIN),它本质上是一个EAGER fetch 指令。在您的情况下,不仅获取了 team2 属性,还获取了它的球员和技能。

【讨论】:

  • 我按照你的建议做了,删除了所有 FetchMode.JOIN 并添加了 LAZY,但我注意到只有两个变化:1. 团队已加载但未完成不同的选择,2. 所有玩家在没有单独选择的情况下加载它们。这很好,但对于每个玩家仍然加载它的统计数据和技能。我检查了几次,没有为它们加载 EAGER,也没有 JOIN fetchMode。
  • 您还需要为 OneToOne 和 ManyToOne 显式设置 LAZY。然后在查询中使用 join fetch。
  • 即使我不需要加载这些实体,我也必须加入获取?
  • 您只需要在需要时加入 FETCH 即可。如果您不必加载它们,只需加载父实体。在这种情况下,所有 LAZY 关联只会在访问时初始化。除非您访问 LAZY 关联,否则它们永远不会触发辅助选择。
  • 抱歉,好像还是不行。我只用那个查询做了测试。在它没有加载之前,可能是以前的提取成功了。不确定。但是,即使按照您在回答代码中提到的那样进行了更改,它仍然会加载团队,选择请求转到 db。
【解决方案2】:

您遇到了一个众所周知的问题,即“N+1 选择”。 简而言之,当您选择父实体时会发生“N+1 选择”问题,并且休眠将使用 OneToOne 为与父实体相关的子实体进行额外选择。因此,如果您在数据库中有“N”条父子记录,hibernate 将获取所有父项选择一次,然后将每个子项获取到单独选择中,总共 N+1 项选择。
hibernate中“N+1”问题有两种解决方法:
1.“加入获取”所有 OneToOne 孩子。
2.开启二级缓存,在OneToOne子节点上使用@Cache注解。

您的问题是您没有“加入获取”所有 OneToOne 孩子。 您必须将它们全部“加入获取”,包括可传递的子代(从子代本身或集合中引用的实体)。

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

【讨论】:

  • @SubSelect 也是一个不错的选择,在使用延迟提取时。 stackoverflow.com/questions/5975752/…
  • 完美答案!如果实体具有 @OneToOne 字段,则必须在查询中获取它们。即使“@OneToOne”字段标有 FetchType.LAZY,它在调用“@Query”时也被视为 EAGER:听起来像是一个错误/任意设计决策 - 这种行为是否有特定原因?
猜你喜欢
  • 2014-03-18
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 2019-07-07
  • 2019-12-30
  • 2023-02-21
  • 1970-01-01
  • 2021-11-26
相关资源
最近更新 更多