如何获取与根实体都与多对一相关的两个实体之间的一对多关系

问题描述 投票:0回答:1

我有一个典型的 N+1 问题。在我的场景中,有多个 OneToMany 关系,我为每个关系解决了 N+1 问题,除了一个。这些是我的实体:

@Data
@Entity
@NoArgsConstructor
public class Person {

    @Id
    @GeneratedValue
    @Column(nullable = false, updatable = false)
    private Long id;

    @OneToMany(mappedBy = "person")
    @JsonManagedReference
    @ToString.Exclude
    private List<Foot> feet;

    @OneToMany(mappedBy = "person")
    @JsonManagedReference
    @ToString.Exclude
    private List<Hand> hands;
}
@Data
@Entity
@NoArgsConstructor
public class Foot {

    @Id
    @GeneratedValue
    @Column(nullable = false, updatable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(nullable = false, updatable = false)
    @JsonBackReference
    private Person person;

    @OneToMany(mappedBy = "foot")
    @JsonManagedReference
    @ToString.Exclude
    private List<Hand> hands;
}
@Data
@Entity
@NoArgsConstructor
public class Hand {

    @Id
    @GeneratedValue
    @Column(nullable = false, updatable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(nullable = false, updatable = false)
    @JsonBackReference
    private Person person;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(nullable = true, updatable = false)
    @JsonBackReference
    private Foot foot;
}

所以基本上我有一个拥有任意数量的脚和手的人。除此之外,一只脚还有任意数量的手。

我能够通过使用两个 JOIN FETCH 查询来解决 Person-Foot 和 Person-Hand 关系的 N+1 问题:

List<Person> personList = repository.getPersonsFetchFeet();
repository.getPersonsFetchHands();
System.out.println(new ObjectMapper().writeValueAsString(personList));

使用存储库方法:


    @Query("SELECT p FROM Person p JOIN FETCH p.feet")
    List<Person> getPersonsFetchFeet();

    @Query("SELECT p FROM Person p JOIN FETCH p.hands")
    List<Person> getPersonsFetchHands();

手脚关系仍然存在N+1问题。所以我添加了另一个存储库方法:

    @Query("SELECT f FROM Foot f JOIN FETCH f.hands")
    List<Foot> getFeetFetchHands();

我在运行其他两个存储库方法后立即运行它:

List<Person> personList = repository.getPersonsFetchFeet();
repository.getPersonsFetchHands();
repository.getFeetFetchHands();
System.out.println(new ObjectMapper().writeValueAsString(personList));

但是,SQL日志仍然显示有很多查询根据foot_id获取Hand实体。这是sql日志,最后一行将被记录数千次:

select p1_0.id,f1_0.person_id,f1_0.id from Person p1_0 join Foot f1_0 on p1_0.id=f1_0.person_id
select p1_0.id,h1_0.person_id,h1_0.id,h1_0.foot_id from Person p1_0 join Hand h1_0 on p1_0.id=h1_0.person_id
select f1_0.id,h1_0.foot_id,h1_0.id,h1_0.person_id,f1_0.person_id from Foot f1_0 join Hand h1_0 on f1_0.id=h1_0.foot_id
select h1_0.foot_id,h1_0.id,h1_0.person_id from Hand h1_0 where h1_0.foot_id=?

据我了解,第3次查询后,所有信息应该都可用了。

如何摆脱这个 N+1 问题?

我将 Spring Boot 3.2.4 与 Spring Data JPA 和 Postgres 结合使用。

hibernate spring-data-jpa jpql
1个回答
0
投票

我偶然找到了解决方案。问题是许多脚没有任何相关的手。因此,他们被排除在查询之外

SELECT f FROM Foot f JOIN FETCH f.hands
。我之前没有意识到,因为每个人至少有一只相关的脚和至少一只相关的手。当我删除一个人的人脚关系时,框架运行了一个额外的查询。

我将所有查询更改为使用

LEFT JOIN FETCH
而不是
JOIN FETCH
,现在它可以工作了。

© www.soinside.com 2019 - 2024. All rights reserved.