Как избавиться от N + 1 с помощью JPA Criteria API в Hibernate - PullRequest
0 голосов
/ 17 октября 2018

Я перевожу наш DAO с использования Hibernate Criteria API на JPA Criteria API.У меня там есть класс с несколькими @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 s:

if (LEFT_OUTER_JOIN_ENTITIES.contains(field)) {
    path = ((From) path).join(field, JoinType.LEFT);
} else {
    path = path.get(field);
}

Я получаю правильные результаты, все записи A и B получены правильно.Однако после миграции у меня возникает проблема n + 1: все записи B извлекаются одна за другой, несмотря на использование LEFT OUTER JOIN в сгенерированных запросах.Ранее (при использовании Hibernate Criteria API) Hibernate мог выполнить запрос без n + 1 с такими же объединениями в сгенерированном SQL.

Спасибо за любые идеи и помощь!

ОБНОВЛЕНИЕ В качестве примера использования if-else выше для поиска по "b1.fieldName" я получу следующее:

criteriaBuilder.equal(root.join("b1", JoinType.LEFT).get("someFiled"), 42)

1 Ответ

0 голосов
/ 23 октября 2018

Проблема N + 1 возникает из стратегии извлечения EAGER по умолчанию из @ManyToOne ассоциаций.

Итак, вам нужно переключиться на 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, которые могут повлиять на другие части вашего приложения, ознакомьтесь с этой статьей .

Если вам нужно получитьохотно связывайтесь с Criteria API, тогда вы должны использовать fetch вместо join.

if (LEFT_OUTER_JOIN_ENTITIES.contains(field)) {
    path = ((From) path).fetch(field, JoinType.LEFT);
} else {
    path = path.get(field);
}
...