尽管在一个非常复杂的软件中使用@Fetch(FetchMode.JOIN),我还是遇到了“N+1 选择问题”,为了简单起见,我创建了一个在 GitHub 上发布的示例。问题是我无法使用JOIN模式来获取数据,该行为总是产生“N+1选择问题”。我的主表中有一个非常大的数据集,在我的例子中,N 约为 10/15k,执行 JOIN 是强制性的。
public class Student {
@Id
private String id;
private String firstName;
private String lastName;
// @ManyToOne
// @JoinColumn(name = "university_id")
// private University university;
}
public class University {
@Id
private String id;
private String name;
private String address;
@ToString.Exclude
@OneToMany/*(fetch = FetchType.EAGER)*/
@Fetch(FetchMode.JOIN)
private Set<Student> students = new HashSet<>();
}
public interface StudentRepository extends JpaRepository<Student, String> {
}
public interface UniversityRepository extends JpaRepository<University, String> {
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootTestClass.class)
@DataJpaTest
@Log4j2
public class FetchTypeTest {
@Autowired
private UniversityRepository repository;
@Test
public void findAll() {
List<University> universityList = repository.findAll(new PageRequest(0, 100)).getContent();
for (University university : universityList) {
log.info("{} [{}]", university.getName(), university.getAddress());
for (Student student : university.getStudents()) {
log.info("{}, {}", student.getLastName(), student.getFirstName());
}
}
}
}
期望看到 1 个且只有 1 个查询(左外)连接大学和学生,但执行测试日志显示 3 选择:大学 1,学生 2(有 2 所大学,每所大学 5 名学生) FetchMode.JOIN 用法。我尝试了 FetchMode / FetchType 的所有可行组合,但没有任何效果
依赖关系:
FindAll*
spring-data-jpa
的方法实现了忽略获取模式的查询。当您编写查询时,您是在告诉什么已连接,什么未连接。
仅当使用
findById
等方法加载实体时或浏览其他实体图并加载其关联时,才会考虑获取模式。
你可以定义这样的方法
@Query(value = "SELECT u FROM University u LEFT JOIN FETCH u.students s order by u.name")
Page<Region> findAllUniversitiesWithPagination(Pageable pageable);
但是注意这个方法是在内存中分页的
HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
按照这篇post来解决这个问题。