У меня проблема с отношениями 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. Это поможет мне в некоторых случаях...
Возможно, вам лучше провести рефакторинг. Как правило, EAGER будет использоваться только для тривиальных отношений, если это так. Лучше иметь все ЛЕНИЮ, а затем получить лишнее, когда вы действительно знаете, что вам это нужно. K.Nicholas 04.01.2016
Привет @Николас! Это лучшее решение. К сожалению, рефакторинг всех объектов невозможен, потому что это сильно влияет на приложение. Dherik 04.01.2016
Дополнительную информацию по этому вопросу можно найти здесь.
Когда вы используете график выборки, только те поля, которые вы поместили в @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 с сопоставлением oneToManyFetchType.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:
Да, вы можете сопоставить два класса сущностей с одной и той же таблицей, и это допустимый обходной путь. Однако остерегайтесь ситуаций, в которых экземпляры обоих типов одновременно присутствуют в одном и том же контексте персистентности, поскольку обновления экземпляра объекта одного типа не отражаются в одном и том же экземпляре другого типа. Также усложняется кэширование второго уровня таких сущностей.
Получить профили также интересны, но в настоящее время они очень ограничены, и вы можете переопределить план/стратегию выборки по умолчанию только с помощью профилей выборки в стиле соединения (вы можете сделать ленивую ассоциацию активной, но не наоборот). Однако вы можете использовать этот трюк, чтобы изменить это поведение: сделать ассоциацию ленивой по умолчанию и включить профиль по умолчанию для всех сеансы/транзакции. Затем отключите профиль в транзакциях, для которых требуется отложенная загрузка.
Ответ 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);