Игнорировать 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)


arrow_upward
13
arrow_downward

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

arrow_upward
8
arrow_downward

Обновление (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

arrow_upward
3
arrow_downward

По умолчанию 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

arrow_upward
2
arrow_downward

На самом деле никогда не пробовал это, но, возможно, стоит попробовать... Предполагая, что фабрика сеансов доступна на уровне 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

arrow_upward
2
arrow_downward

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

arrow_upward
1
arrow_downward

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

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

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

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

arrow_upward
0
arrow_downward

Во-первых, вы не можете переопределить 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