如何在 Hibernate 中使用 JPA Criteria API 摆脱 N+1
How to get rid of N+1 with JPA Criteria API in Hibernate
我正在将我们的 DAO 从使用 Hibernate Criteria API 迁移到 JPA Criteria API。我有一个 class 和几个 @ManyToOne
那里:
@Entity
@Table(name = "A_TABLE", schema="SCHEMA_NAME")
public class A {
@ManyToOne
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '1' OR B.B_CODE = '2') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b1;
@ManyToOne
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '3' OR B.B_CODE = '4') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b2;
...
}
@Entity
@Table(name = "B_TABLE", schema="SCHEMA_NAME")
public class B {
}
在查询中,我使用 JoinType.LEFT
以摆脱默认生成的 CROSS JOIN
:
if (LEFT_OUTER_JOIN_ENTITIES.contains(field)) {
path = ((From) path).join(field, JoinType.LEFT);
} else {
path = path.get(field);
}
我得到了正确的结果,所有 A
和 B
记录都被正确检索。但是,在迁移之后,我遇到了 n+1 个问题:尽管在生成的查询中使用了 LEFT OUTER JOIN
,但所有 B
记录都被逐条检索。以前(当使用 Hibernate Criteria API 时)Hibernate 能够满足查询而无需 n+1 在生成的 SQL.
中具有相同的连接
感谢您的任何想法和帮助!
UPDATE 作为使用上面的 if-else
通过 "b1.fieldName" 进行搜索的示例,我将得到以下内容:
criteriaBuilder.equal(root.join("b1", JoinType.LEFT).get("someFiled"), 42)
N+1 问题来自 @ManyToOne
关联的默认 FetchType.EAGER
提取策略。
所以,你需要切换到 FetchType.LAZY
,像这样:
@ManyToOne(fetch = FetchType.LAZY)
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '1' OR B.B_CODE = '2') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b1;
@ManyToOne(fetch = FetchType.LAZY)
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '3' OR B.B_CODE = '4') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b2;
如果您想自动检测可能影响应用程序其他部分的 N+1 个问题,那么您可以使用 datasource-proxy
。
如果您需要使用 Criteria API 急切地获取关联,那么您应该使用 fetch
而不是 join
。
if (LEFT_OUTER_JOIN_ENTITIES.contains(field)) {
path = ((From) path).fetch(field, JoinType.LEFT);
} else {
path = path.get(field);
}
我正在将我们的 DAO 从使用 Hibernate Criteria API 迁移到 JPA Criteria API。我有一个 class 和几个 @ManyToOne
那里:
@Entity
@Table(name = "A_TABLE", schema="SCHEMA_NAME")
public class A {
@ManyToOne
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '1' OR B.B_CODE = '2') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b1;
@ManyToOne
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '3' OR B.B_CODE = '4') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b2;
...
}
@Entity
@Table(name = "B_TABLE", schema="SCHEMA_NAME")
public class B {
}
在查询中,我使用 JoinType.LEFT
以摆脱默认生成的 CROSS JOIN
:
if (LEFT_OUTER_JOIN_ENTITIES.contains(field)) {
path = ((From) path).join(field, JoinType.LEFT);
} else {
path = path.get(field);
}
我得到了正确的结果,所有 A
和 B
记录都被正确检索。但是,在迁移之后,我遇到了 n+1 个问题:尽管在生成的查询中使用了 LEFT OUTER JOIN
,但所有 B
记录都被逐条检索。以前(当使用 Hibernate Criteria API 时)Hibernate 能够满足查询而无需 n+1 在生成的 SQL.
感谢您的任何想法和帮助!
UPDATE 作为使用上面的 if-else
通过 "b1.fieldName" 进行搜索的示例,我将得到以下内容:
criteriaBuilder.equal(root.join("b1", JoinType.LEFT).get("someFiled"), 42)
N+1 问题来自 @ManyToOne
关联的默认 FetchType.EAGER
提取策略。
所以,你需要切换到 FetchType.LAZY
,像这样:
@ManyToOne(fetch = FetchType.LAZY)
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '1' OR B.B_CODE = '2') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b1;
@ManyToOne(fetch = FetchType.LAZY)
@JoinFormula("(SELECT * FROM (SELECT B.B_ID FROM SCHEMA_NAME.B_TABLE B WHERE B.A_ID = B_ID AND (B.B_CODE = '3' OR B.B_CODE = '4') ORDER BY B.B_CREATED_TIMESTAMP DESC) WHERE ROWNUM = 1)")
private B b2;
如果您想自动检测可能影响应用程序其他部分的 N+1 个问题,那么您可以使用 datasource-proxy
。
如果您需要使用 Criteria API 急切地获取关联,那么您应该使用 fetch
而不是 join
。
if (LEFT_OUTER_JOIN_ENTITIES.contains(field)) {
path = ((From) path).fetch(field, JoinType.LEFT);
} else {
path = path.get(field);
}