JPA нетерпеливый выборка не присоединяется

112

Что именно контролирует стратегия извлечения JPA? Я не вижу разницы между нетерпеливым и ленивым. В обоих случаях JPA / Hibernate не присоединяется автоматически к отношениям «многие к одному».

Пример: у человека один адрес. Адрес может принадлежать многим людям. Аннотированные классы сущностей JPA выглядят так:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Если я использую запрос JPA:

select p from Person p where ...

JPA / Hibernate генерирует один запрос SQL для выбора из таблицы Person, а затем отдельный запрос адреса для каждого человека:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

Это очень плохо для больших наборов результатов. Если есть 1000 человек, он генерирует 1001 запрос (1 от Person и 1000 от Address). Я знаю это, потому что смотрю журнал запросов MySQL. Насколько я понимаю, установка типа выборки адреса на нетерпеливое приведет к тому, что JPA / Hibernate автоматически запросит соединение. Однако, независимо от типа выборки, он по-прежнему генерирует отдельные запросы для отношений.

Только когда я явно говорю ему присоединиться, он действительно присоединяется:

select p, a from Person p left join p.address a where ...

Я что-то упустил? Теперь мне нужно передать код для каждого запроса, чтобы он оставался соединенным с отношениями «многие к одному». Я использую реализацию JPA Hibernate с MySQL.

Изменить: похоже (см. Часто задаваемые вопросы о Hibernate здесь и здесь ), что FetchTypeне влияет на запросы JPA. Так что в моем случае я прямо сказал ему присоединиться.

Стив Куо
источник
5
Ссылки на записи FAQ сломаны, здесь работает один
n0weak

Ответы:

99

JPA не предоставляет никаких спецификаций по отображению аннотаций для выбора стратегии выборки. Как правило, связанные сущности можно получить любым из способов, указанных ниже.

  • SELECT => один запрос для корневых сущностей + один запрос для связанной сопоставленной сущности / коллекции каждой корневой сущности = (n + 1) запросов
  • SUBSELECT => один запрос для корневых сущностей + второй запрос для связанной сопоставленной сущности / коллекции всех корневых сущностей, полученных в первом запросе = 2 запроса
  • JOIN => один запрос для извлечения как корневых сущностей, так и всех их сопоставленных сущностей / коллекция = 1 запрос

Так SELECTи JOINдве крайности и SUBSELECTпадает между ними. Можно выбрать подходящую стратегию на основе модели предметной области.

По умолчанию SELECTиспользуется как JPA / EclipseLink, так и Hibernate. Это можно изменить, используя:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

в Hibernate. Это также позволяет SELECTявно установить режим, @Fetch(FetchMode.SELECT)который можно настроить, используя размер пакета, например @BatchSize(size=10).

Соответствующие аннотации в EclipseLink:

@JoinFetch
@BatchFetch
Яду
источник
5
Почему существуют эти настройки? Я думаю, что JOIN нужно использовать почти всегда. Теперь мне нужно пометить все сопоставления аннотациями для спящего режима.
vbezhenar 01
4
Интересно, но, к сожалению, @Fetch (FetchMode.JOIN) вообще не работает для меня (Hibernate 4.2.15) в JPQL, как в Criteria.
Афакс,
3
Аннотации Hibernate, похоже, вообще не работают для меня, используя Spring JPA
TheBakker
2
@Aphax Это может быть связано с тем, что Hibernate использует разные стратегии по умолчанию для JPAQL / Criteria и em.find (). См. Vladmihalcea.com/2013/10/17/… и справочную документацию.
Джошуа Дэвис
1
@vbezhenar (и другие читают его комментарий через некоторое время): запрос JOIN генерирует декартово произведение в базе данных, поэтому вы должны быть уверены, что хотите, чтобы этот декартово произведение было вычислено. Обратите внимание, что если вы используете соединение выборки, даже если вы установите LAZY, оно будет загружено с нетерпением.
Walfrat
45

"mxc" правильно. fetchTypeпросто указывает, когда должно быть разрешено отношение.

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

@Fetch(FetchMode.JOIN)

на свое поле. Это специальная аннотация для гибернации.

Рудольфсон
источник
6
У меня это не работает с Hibernate 4.2.15, в JPQL или Criteria.
Афакс,
6
@Aphax Я думаю, это потому, что JPAQL и критерии не подчиняются спецификации Fetch. Аннотация Fetch работает только для em.find (), AFAIK. См. Vladmihalcea.com/2013/10/17/… Также см. Документацию по гибернации. Я почти уверен, что это где-то покрыто.
Джошуа Дэвис
@JoshuaDavis Я имею в виду, что аннотация \ @Fetch не применяет какую-либо оптимизацию JOIN в запросах, будь то JPQL или em.find (), я только что попробовал Hibernate 5.2. +, И он все тот же
Aphax 01
38

Атрибут fetchType определяет, извлекается ли аннотированное поле сразу после выборки основного объекта. Это не обязательно диктует, как создается оператор выборки, фактическая реализация sql зависит от поставщика, который вы используете toplink / hibernate и т. Д.

Если вы установите fetchType=EAGERЭто означает, что аннотированное поле заполняется своими значениями одновременно с другими полями в сущности. Поэтому, если вы открываете entitymanager, извлекаете свои объекты person, а затем закрываете entitymanager, последующее выполнение person.address не приведет к возникновению исключения ленивой загрузки.

Если вы установите fetchType=LAZY поле заполняется только при доступе к нему. Если к этому моменту вы закрыли entitymanager, будет сгенерировано исключение ленивой загрузки, если вы введете person.address. Чтобы загрузить поле, вам нужно вернуть объект в контекст entitymangers с помощью em.merge (), затем выполнить доступ к полю и затем закрыть entitymanager.

При создании класса клиентов с коллекцией заказов клиентов может потребоваться отложенная загрузка. Если вы извлекали каждый заказ для клиента, когда хотели получить список клиентов, это может быть дорогостоящей операцией с базой данных, когда вы ищете только имя клиента и контактные данные. Лучше оставить доступ к БД на потом.

По второй части вопроса - как заставить спящий режим генерировать оптимизированный SQL?

Hibernate должен позволить вам давать подсказки относительно того, как построить наиболее эффективный запрос, но я подозреваю, что что-то не так с построением вашей таблицы. Установлены ли отношения в таблицах? Hibernate, возможно, решил, что простой запрос будет быстрее, чем соединение, особенно если отсутствуют индексы и т. Д.

mxc
источник
20

Попробуйте:

select p from Person p left join FETCH p.address a where...

У меня это работает аналогично JPA2 / EclipseLink, но похоже, что эта функция присутствует и в JPA1 :

Sinuhepop
источник
7

Если вы используете EclipseLink вместо Hibernate, вы можете оптимизировать свои запросы с помощью «подсказок». См. Эту статью в Eclipse Wiki: EclipseLink / Примеры / JPA / QueryOptimization .

Есть глава о «Совместном чтении».

uı6ʎɹnɯ ꞁəıuɐp
источник
2

чтобы присоединиться, вы можете делать несколько вещей (используя eclipselink)

  • в jpql вы можете выполнить выборку с левым соединением

  • в именованном запросе вы можете указать подсказку запроса

  • в TypedQuery вы можете сказать что-то вроде

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • есть также подсказка по пакетной выборке

    query.setHint("eclipselink.batch", "e.address");

видеть

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html

Кальпеш Сони
источник
1

У меня была именно эта проблема, за исключением того, что класс Person имел встроенный ключевой класс. Мое собственное решение заключалось в том, чтобы присоединиться к ним в запросе И удалить

@Fetch(FetchMode.JOIN)

Мой встроенный класс идентификатора:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}
Gunslinger
источник
-1

Мне приходят в голову две вещи.

Во-первых, вы уверены, что имеете в виду ManyToOne для адреса? Это означает, что несколько человек будут иметь один и тот же адрес. Если он отредактирован для одного из них, он будет отредактирован для всех. Это ваше намерение? 99% времени адреса являются «частными» (в том смысле, что они принадлежат только одному человеку).

Во-вторых, есть ли у вас какие-либо другие активные отношения с сущностью Person? Если я правильно помню, Hibernate может обрабатывать только одно стремительное отношение к объекту, но это, возможно, устаревшая информация.

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

Cletus
источник
Это выдуманный пример, в котором используется соединение многие к одному. Личный адрес, возможно, был не лучшим примером. Я не вижу в своем коде других активных типов выборки.
Стив Куо,
Я предлагаю свести это к простому примеру, который запускает и делает то, что вы видите, а затем опубликуйте это. В вашей модели могут быть другие сложности, вызывающие неожиданное поведение.
cletus
1
Я запустил код точно так, как он показан выше, и он демонстрирует указанное поведение.
Стив Куо,