Нетерплячий збір 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.

Редагувати: З'являється (див. FAQ про сплячку тут і тут ), що FetchTypeне впливає на запити JPA. Тому в моєму випадку я прямо кажу йому приєднатися.


5
Посилання на записи поширених запитань порушені, ось працює один
n0weak

Відповіді:


99

JPA не надає жодних специфікацій щодо відображення анотацій для вибору стратегії отримання. Загалом, пов'язані об'єкти можуть бути отримані будь-яким із способів, наведених нижче

  • SELECT => один запит для кореневих сутностей + один запит для пов'язаного відображеного сутності / колекції кожного кореневого об'єкта = (n + 1) запитів
  • SUBSELECT => один запит для кореневих сутностей + другий запит для пов’язаного відображеного сутності / колекції всіх кореневих сутностей, отриманих у першому запиті = 2 запити
  • ПРИЄДНАЙТЕСЬ => один запит для отримання обох кореневих сутностей та всіх їх відображених об'єктів / колекції = 1 запит

Так SELECTі JOINє дві крайності і SUBSELECTпадіння між ними. Можна вибрати відповідну стратегію, спираючись на її / його доменну модель.

За замовчуванням SELECTвикористовується і JPA / EclipseLink та Hibernate. Це можна скасувати за допомогою:

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

в сплячому режимі. Це також дозволяє SELECTчітко встановити режим, використовуючи @Fetch(FetchMode.SELECT)який можна настроїти, використовуючи розмір партії, наприклад @BatchSize(size=10).

Відповідні примітки в EclipseLink:

@JoinFetch
@BatchFetch

5
Чому ці налаштування існують? Я думаю, що JOIN потрібно використовувати майже завжди. Тепер мені потрібно позначити всі карти зі специфічними для зимування анотаціями.
вбеженар

4
Цікаво, але, на жаль, @Fetch (FetchMode.JOIN) для мене взагалі не працює (Hibernate 4.2.15), як у JPQL, так і в критеріях.
Афакс

3
Анотація на
сплячку,

2
@Aphax Це може бути тому, що Hibernate використовує різні стратегії за замовчуванням для JPAQL / Критерії проти em.find (). Див. Vladmihalcea.com/2013/10/17/… та довідкову документацію.
Джошуа Девіс

1
@vbezhenar (а інший читає його коментар через деякий час): ПРИЄДНАЙТЕСЬ запит генерувати декартовий продукт у базі даних, тому ви повинні бути впевнені, що ви хочете, щоб цей декартовий продукт був обчислений. Зауважте, що якщо ви користуєтеся приєднанням для завантаження, навіть якщо ви поставите LAZY, він буде нетерпляче завантажений.
Вальфрат

45

"mxc" правильно. fetchTypeпросто вказує, коли відносини слід вирішити.

Щоб оптимізувати прагнення до завантаження за допомогою зовнішнього з'єднання, яке потрібно додати

@Fetch(FetchMode.JOIN)

до вашого поля. Це специфічна анотація на сплячку.


6
Це не працює для мене в режимі hibernate 4.2.15 в JPQL або критеріях.
Афакс

6
@Aphax Я думаю, що це тому, що JPAQL і критерії не відповідають специфікації Fetch. Анотація "Вилучити" працює лише для em.find (), AFAIK. Див. Vladmihalcea.com/2013/10/17/… Також дивіться сплячі доки. Я впевнений, що це десь висвітлено.
Джошуа Девіс

@JoshuaDavis Що я маю на увазі, що анотація \ @Fetch не застосовує будь-якої оптимізації JOIN у запитах, коли JPQL чи em.find (), я просто спробував ще в Hibernate 5.2. + І все одно
Афакс

38

Атрибут fetchType керує тим, чи отримується анотоване поле негайно при отриманні основного об'єкта. Це не обов'язково диктує, як будується оператор вибору, фактична реалізація sql залежить від постачальника, якого ви використовуєте toplink / hibernate тощо.

Якщо встановити fetchType=EAGERЦе означає, що анотоване поле заповнюється своїми значеннями одночасно з іншими полями в сутності. Отже, якщо ви відкриєте entmanager, отримайте об'єкти своєї особи, а потім закрийте entmanager, згодом це зробить person.address, що не призведе до того, що буде викинуто виняток ледачого навантаження.

Якщо встановлено, fetchType=LAZYполе заповнюється лише тоді, коли доступ до нього. Якщо ви закрили entmanager, тоді виключення з ледачого навантаження буде викинуто, якщо ви робите person.address. Щоб завантажити поле, вам потрібно повернути сутність у контекст управління об'єктами за допомогою em.merge (), потім зробити доступ до поля та потім закрити супровід управління суттю.

Можливо, вам захочеться ліниве завантаження, будуючи клас клієнтів із колекцією для замовлень клієнтів. Якщо ви отримували кожне замовлення для клієнта, коли хотіли отримати список клієнтів, це може бути дорогою операцією з базою даних, коли ви шукаєте лише ім’я та контактні дані клієнта. Найкраще залишити доступ до db пізніше.

У другій частині питання - як перейти в сплячку для генерації оптимізованого SQL?

Hibernate повинен дозволити вам надати підказки щодо побудови найбільш ефективного запиту, але я підозрюю, що у вашій конструкції таблиці щось не так. Чи встановлено взаємозв'язок у таблицях? Hibernate, можливо, вирішив, що простий запит буде швидшим, ніж об'єднання, особливо якщо відсутні індекси тощо.


20

Спробуйте:

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

Для мене це працює аналогічно JPA2 / EclipseLink, але, схоже, ця функція присутня і в JPA1 :



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)

Мій вбудований клас 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();
    }

}

-1

У мене трапляються дві речі.

По-перше, ви впевнені, що маєте на увазі ManyToOne для адреси? Це означає, що кілька людей матимуть однакову адресу. Якщо він відредагований для одного з них, він буде відредагований для всіх. Це ваш намір? 99% адрес часу є "приватними" (в тому сенсі, що вони належать лише одній особі).

По-друге, чи є у вас інші нетерплячі стосунки з особою? Якщо я правильно пам'ятаю, Hibernate може обробляти лише одне прагнення до відносин на підприємстві, але це, можливо, застаріла інформація.

Я говорю це, оскільки ваше розуміння того, як це має працювати, по суті є правильним з того місця, де я сиджу.


Це складений приклад, який використовує багато-до-одного. Адреса людини, можливо, не був найкращим прикладом. У моєму коді я не бачу інших бажаючих типів пошуку.
Стів Куо

Тоді я пропоную звести це до простого прикладу, який працює і робить те, що ви бачите, а потім опублікуйте це. У вашій моделі можуть виникнути інші ускладнення, які викликають несподівану поведінку.
клент

1
Я запустив код точно так, як він показано вище, і він проявляє зазначену поведінку.
Стів Куо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.