JPA нетерпеливый выбор не присоединяется - PullRequest
101 голосов
/ 21 января 2009

Что именно контролирует стратегия извлечения 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, а затем отдельный адресный запрос для каждого 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 от человека и 1000 отличается от адреса). Я знаю это, потому что я просматриваю журнал запросов MySQL. Насколько я понимаю, установка типа извлечения адреса на eager приведет к тому, что JPA / Hibernate автоматически запросит соединение. Однако независимо от типа выборки он по-прежнему генерирует различные запросы для отношений.

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

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

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

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

Ответы [ 8 ]

89 голосов
/ 18 июня 2012

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

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

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

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

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

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

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

@JoinFetch
@BatchFetch
43 голосов
/ 08 мая 2009

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

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

@Fetch(FetchMode.JOIN)

на ваше поле. Это специфическая для спящего аннотация.

36 голосов
/ 02 февраля 2009

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

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

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

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

Для второй части вопроса - как получить спящий режим для генерации оптимизированного SQL?

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

18 голосов
/ 10 марта 2011

Попробуйте с:

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

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

7 голосов
/ 20 августа 2009

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

Есть глава о "Присоединенном чтении".

2 голосов
/ 02 июня 2014

Чтобы присоединиться, вы можете сделать несколько вещей (используя 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 / партии сгрузить-оптимизации объектно-graph.html

1 голос
/ 12 декабря 2011

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

@Fetch(FetchMode.JOIN)

Мой встроенный класс ID:

@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();
    }

}
0 голосов
/ 21 января 2009

Две вещи происходят со мной.

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...