Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: DherikОтветов: 7

У меня проблема с отношениями 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. Это поможет мне в некоторых случаях...

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

javahibernatejpahqljpql

Ответы

Ответ 1 · рейтинг 13 · Milan

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

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

Ответ 2 · рейтинг 8 · Dherik

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

Ответ 3 · рейтинг 3 · Madhusudana Reddy Sunnapu

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

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

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

Ответ 4 · рейтинг 2 · Attila T

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

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

Ответ 5 · рейтинг 2 · Dragan Bozanovic

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

Ответ 6 · рейтинг 1 · Nathan

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

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

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

Ответ 7 · Caner Yeşildağ

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

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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы