问题
当您忘记获取关联然后您需要访问它时,会发生 N+1 查询问题。
例如,假设我们有以下 JPA 查询:
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
where pc.review = :review
""", PostComment.class)
.setParameter("review", review)
.getResultList();
现在,如果我们迭代 PostComment 实体并遍历 post 关联:
for(PostComment comment : comments) {
LOGGER.info("The post title is '{}'", comment.getPost().getTitle());
}
Hibernate 会生成以下 SQL 语句:
SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_
FROM post_comment pc
WHERE pc.review = 'Excellent!'
INFO - Loaded 3 comments
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 1
INFO - The post title is 'Post nr. 1'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 2
INFO - The post title is 'Post nr. 2'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 3
INFO - The post title is 'Post nr. 3'
这就是 N+1 查询问题的产生方式。
因为在获取PostComment实体时post关联没有初始化,所以Hibernate必须通过二级查询获取Post实体,对于N个PostComment实体,还要执行N个查询(因此 N+1 查询问题)。
修复
解决此问题需要做的第一件事是添加[正确的 SQL 日志记录和监控][1]。如果没有记录,您在开发某个功能时不会注意到 N+1 查询问题。
其次,要修复它,您可以 JOIN FETCH 导致此问题的关系:
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
join fetch pc.post p
where pc.review = :review
""", PostComment.class)
.setParameter("review", review)
.getResultList();
如果您需要获取多个子关联,最好在初始查询中获取一个集合,并在辅助 SQL 查询中获取第二个集合。
如何自动检测N+1查询问题
这个问题最好通过集成测试来发现。
您可以使用自动 JUnit 断言来验证生成的 SQL 语句的预期计数。 db-util project 已经提供了这个功能,并且它是开源的,并且依赖项在 Maven Central 上可用。