我有一个典型的 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 结合使用。
我偶然找到了解决方案。问题是许多脚没有任何相关的手。因此,他们被排除在查询之外
SELECT f FROM Foot f JOIN FETCH f.hands
。我之前没有意识到,因为每个人至少有一只相关的脚和至少一只相关的手。当我删除一个人的人脚关系时,框架运行了一个额外的查询。
我将所有查询更改为使用
LEFT JOIN FETCH
而不是 JOIN FETCH
,现在它可以工作了。