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

Что именно делает контроль стратегии 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 ...

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

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

Ответ 1

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

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

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

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

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

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

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

@JoinFetch
@BatchFetch

Ответ 2

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

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

@Fetch(FetchMode.JOIN)

в поле. Это аннотация для спящего режима.

Ответ 3

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

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

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

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

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

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

Ответ 4

Попробуйте:

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

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

Ответ 5

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

Существует глава о "Присоединении к чтению".

Ответ 6

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

  • в jpql вы можете сделать левое соединение fetch

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

  • в 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

Ответ 7

У меня была именно эта проблема, за исключением того, что класс 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();
    }

}

Ответ 8

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

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

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

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