Игнорировать FetchType.EAGER в отношениях

У меня проблема с отношениями EAGERs в большом приложении. Некоторые объекты в этом приложении имеют ассоциации EAGER с другими объектами. Это становится "ядом" в некоторых функциях.

Теперь моей команде нужно оптимизировать эту функциональность, но мы не можем изменить тип выборки на LAZY, потому что нам нужно будет реорганизовать все приложение.

Итак, мой вопрос: есть ли способ сделать конкретный запрос, игнорируя ассоциации EAGERs в моем возвращенном объекте?

Пример: когда у меня есть этот объект Person, я бы не хотел приносить список адресов, когда я делаю запрос, чтобы найти Person.

@Entity
public class Person {

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;

}

Query query = EntityManager.createQuery("FROM Person person");
//list of person without the address list! But how???
List<Person> resultList = query.getResultList();

Спасибо!

Обновление

Единственный способ, которым я нашел, не возвращает объект, возвращая только некоторые поля объекта. Но я хотел бы найти решение, в котором я могу вернуть объект (в моем примере, объект Person).

Я думаю, если можно сопоставить одну и ту же таблицу дважды в Hibernate. Таким образом, я могу сопоставлять одну и ту же таблицу без ассоциаций EAGER. Это поможет мне в нескольких случаях...

Ответы

Ответ 1

После всех этих лет переопределить отображение EAGER в Hibernate пока невозможно. Из последней документации Hibernate (5.3.10.Final):

Хотя в стандарте JPA указано, что вы можете переопределить сопоставление извлечения EAGER во время выполнения с помощью подсказки javax.persistence.fetchgraph, в настоящее время Hibernate не реализует эту функцию, поэтому сопоставления EAGER нельзя извлекать лениво. Для получения дополнительной информации, проверьте проблему HHH-8776 Jira.

При выполнении запроса JPQL, если связь EAGER опущена, Hibernate выдаст вторичный выбор для каждой ассоциации, которую нужно срочно получить, что может привести к проблемам с запросом N + 1.

По этой причине лучше использовать LAZY-ассоциации и получать их с нетерпением только для каждого запроса.

А также:

Стратегия извлечения EAGER не может быть перезаписана для каждого отдельного запроса, поэтому связь всегда будет получена, даже если она вам не нужна. Более того, если вы забудете ПРИСОЕДИНИТЬСЯ к извлечению ассоциации EAGER в запросе JPQL, Hibernate инициализирует ее с помощью вторичного оператора, что, в свою очередь, может привести к проблемам с запросом N + 1.

Ответ 2

Если вы используете JPA 2.1 (Hibernate 4.3+), вы можете добиться того, чего хотите с помощью @NamedEntityGraph.

В принципе, вы должны аннотировать свою сущность следующим образом:

@Entity
@NamedEntityGraph(name = "Persons.noAddress")
public class Person {

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;

}

И затем используйте подсказки для извлечения Лица без адреса, например:

EntityGraph graph = this.em.getEntityGraph("Persons.noAddress");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

return this.em.findAll(Person.class, hints);

Подробнее об этом можно найти здесь.

Когда вы используете граф выборки, вы получите только поля, которые вы внесете внутри @NamedEntityGraph.

Все ваши существующие запросы, которые выполняются без подсказки, останутся прежними.

Ответ 3

Никогда не пытался это сделать, но мог бы сделать снимок... Предполагая, что сеанс factory доступен на уровне DAO посредством инъекции или любым другим способом, вы можете реализовать что-то подобное в (возможно новом) методе DAO:

List<Person> result = (List<Person>) sessionFactory.getCurrentSession()
        .createCriteria(Person.class)
        .setFetchMode("address", FetchMode.LAZY)
        .list();
return result;

Ответ 4

По умолчанию Hibernate HQL, Criteria и NativeSQL дают нам гибкость для EAGERly загрузки коллекции, если она отображена как LAZY в модели домена.

Что касается другого пути, т.е. сопоставление коллекции как EAGER в модели домена и попытка сделать LAZY-загрузку с использованием HQL, Criteria или NativeSQL, я не мог найти прямой или более простой способ, которым мы можем встретить это с помощью HQL/Criteria/NativeSQL.

Хотя мы имеем FetchMode.LAZY, что мы можем установить критерии, он устарел и эквивалентен FetchMode.SELECT. И эффективно FetchMode.LAZY фактически приводит к запуску дополнительного запроса SELECT и все еще с нетерпением загружает коллекцию.

Но, если мы хотим LAZY загружать коллекцию, которая отображается как EAGER, вы можете попробовать это решение: Сделайте HQL/Criteria/NativeSQL вернувшим скалярные значения и используйте ResultTransformer(Transformers.aliasToBean(..)), чтобы вернуть объект сущности (или DTO) с полями, заполненными скалярными значениями.

В моем сценарии у меня есть объект Лес, который имеет коллекцию объектов Tree с отображением oneToMany FetchType.EAGER и FetchMode.JOIN. Чтобы загрузить только объект Лесной без загрузки каких-либо деревьев, я использовал следующий запрос HQL с скалярными значениями и Transformers.aliasToBean(...). Это работает с Criteria и Native SQL, а также использует скаляры и aliasToBean Transformer.

Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();

Я тестировал этот простой запрос, и он может работать, проверяя, подходит ли это для сложных случаев и подходит для всех ваших случаев использования.

Было бы интересно узнать, есть ли лучший или простой способ сделать это, особенно без скаляров и трансформаторов.

Ответ 5

  • Да, вы можете сопоставить два класса сущностей с одной и той же таблицей и это допустимое обходное решение. Однако будьте осторожны в ситуациях, когда экземпляры обоих типов одновременно присутствуют в одном контексте персистентности, поскольку обновления экземпляра объекта одного типа не отражаются на одном экземпляре другого типа. Кроме того, кэширование второго уровня таких объектов усложняется.
  • Получить профили также интересны, но в настоящее время они очень ограничены, и вы можете переопределить план/стратегию выборки по умолчанию только с помощью профили извлечения в стиле соединения (вы можете сделать ленивую ассоциацию нетерпеливой, но не наоборот). Однако вы можете использовать этот трюк, чтобы инвертировать это поведение: по умолчанию сделать объединение ленивым и включить профиль по умолчанию для всех сеансов/транзакций. Затем отключить профиль в транзакциях, в которых вы хотите ленивую загрузку.

Ответ 6

Вы не сказали, почему вы не могли перейти от нетерпения к лени. У меня, однако, сложилось впечатление, что это было по соображениям производительности, и поэтому я хочу подвергнуть сомнению это предположение. Если это так, рассмотрите следующее. Я понимаю, что этот ответ строго не отвечает на ваш вопрос, и он нарушает условие отсутствия ленивых нагрузок, но вот альтернатива, которая отражает мой подход команды разработчиков к тому, что, по моему мнению, является одной и той же основной проблемой.

Задайте тип выборки ленивым, но затем установите аннотацию @BatchSize. Поскольку hibernate обычно использует отдельный запрос БД для загрузки коллекции, это поддерживает это поведение, но с настроенным BatchSize, вы избегаете 1 запроса на элемент (например, в цикле) - при условии, что ваш сеанс все еще открыт.

Поведение для backref отношений OneToOne становится немного забавным (ссылка на сторону - сторона без внешнего ключа). Но для другой стороны OneToOne, для OneToMany и ManyToOne, это дает конечный результат, который, я думаю, вы, вероятно, захотите: вы только запрашиваете таблицы, если они вам действительно нужны, но вы избегаете ленивой загрузки на запись, а вы не должны явно настроить каждый вариант использования. Это означает, что ваша производительность будет оставаться сопоставимой в случае, если вы выполняете ленивую нагрузку, но эта загрузка не происходит, если вам это действительно не нужно.