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

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

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

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

Пример: когда у меня есть этот объект 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. Это поможет мне в некоторых случаях...


person Dherik    schedule 25.07.2013    source источник
comment
stackoverflow.com/questions/10997321/   -  person K.Nicholas    schedule 04.01.2016
comment
Возможно, вам лучше провести рефакторинг. Как правило, EAGER будет использоваться только для тривиальных отношений, если это так. Лучше иметь все ЛЕНИЮ, а затем получить лишнее, когда вы действительно знаете, что вам это нужно.   -  person K.Nicholas    schedule 04.01.2016
comment
Привет @Николас! Это лучшее решение. К сожалению, рефакторинг всех объектов невозможен, потому что это сильно влияет на приложение.   -  person Dherik    schedule 04.01.2016


Ответы (7)


Если вы используете 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;

}

А затем используйте подсказки, чтобы получить Person без адреса, например:

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, будут загружены с готовностью.

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

person Milan    schedule 31.12.2015
comment
На практике это не работает, Hibernate по-прежнему будет извлекать атрибуты, помеченные как EAGER, даже если вы не включите их в свой EntityGraph, см.: hibernate.atlassian.net/browse/HHH-8776 Этот ответ не может быть проверен, поскольку он не работает должным образом. - person Robert Hunt; 19.10.2017
comment
Hibernate теперь корректно реализует EntityGraphs в версии 5.4.22 (см. /HHH-8776?focusedCommentId=106092 и hibernate.atlassian.net /browse/HHH-8776?focusedCommentId=107330). Я успешно протестировал его с 5.4.27, он не загружал поля, которые были статически помечены как EAGER. - person Matze; 21.01.2021

Обновление (06.09.2020):

проблема была решена в версии 5.4.11. Я не могу протестировать прямо сейчас, но ожидается, что атрибуты графов сущностей JPA, не включенные в граф, должны оставаться незагруженными, даже если они объявлены EAGER.

Исходный ответ

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

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

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

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

И:

Стратегия выборки EAGER не может быть перезаписана для каждого запроса, поэтому ассоциация будет извлечена всегда, даже если она вам не нужна. Более того, если вы забудете JOIN FETCH ассоциации EAGER в запросе JPQL, Hibernate инициализирует ее вторичным оператором, что, в свою очередь, может привести к проблемам с запросом N+1.

person Dherik    schedule 20.12.2018

По умолчанию Hibernate HQL, Criteria и NativeSQL дают нам возможность СРОЧНО загружать коллекцию, если она отображается как LAZY в модели предметной области.

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

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

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

В моем сценарии у меня есть объект Forest с набором объектов Tree с сопоставлением oneToMany FetchType.EAGER и FetchMode.JOIN. Чтобы загрузить только объект Forest без загрузки каких-либо деревьев, я использовал следующий запрос 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();

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

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

person Madhusudana Reddy Sunnapu    schedule 31.12.2015

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

List<Person> result = (List<Person>) sessionFactory.getCurrentSession()
        .createCriteria(Person.class)
        .setFetchMode("address", FetchMode.LAZY)
        .list();
return result;
person Attila T    schedule 25.07.2013
comment
Согласно документам hibernate, FetchMode.LAZY устарел и эквивалентен FetchMode.SELECT. Таким образом, FetchMode.LAZY фактически приводит к запуску дополнительного запроса SELECT для загрузки коллекции. Это действительно не загружает коллекцию LAZYily. - person Madhusudana Reddy Sunnapu; 31.12.2015

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

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

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

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

person Nathan    schedule 31.12.2015
comment
это для производительности! У нас много компаний с EAGER. Ваше решение не дает прямого ответа, но является очень хорошей альтернативой для решения проблемы с производительностью в некоторых случаях, когда EAGER повсюду, как в моем случае. - person Dherik; 06.01.2016

Во-первых, вы не можете переопределить FetchType.EAGER. Но вы можете использовать другой способ.

Создайте новый дополнительный класс PersonVo.

public class PersonVo {

private String name;

public PersonVo(String name){
   this.name = name;
}

// getter and setter

}

После того, как вы можете написать запрос ниже в интерфейсе репозитория JPA. Этот запрос вернет ваш класс PersonVo без адреса. com.bla.bla.PersonVo — это путь к вашему классу PersonVo.

@Query("SELECT NEW com.bla.bla.PersonVo(p.name) from Person p where p.id in :idList")
List<PersonVo> findAllById(@Param("idList") List<Long> idList);

Когда я попробовал этот способ, это сработало.

person Caner Yeşildağ    schedule 24.07.2020