Hibernate и Spring Boot с GraphQL - загружать только выбранные значения

В настоящее время я использую GraphQL SPQR с Spring Boot. Чтобы повысить производительность базы данных, я написал пользовательскую логику, используя @GraphQLEnvironment ResolutionEnvironment, из этого я смог найти все запрошенные значения и отфильтровать их для аннотации @Entity, затем я использовал их для создания динамического EntityGraph, который используется для Eager fetch эти конкретные ценности.

Теперь, похоже, это работает для Entities, отображаемых как @OneToMany, однако мои @ManyToOne и @OneToOne Entities по-прежнему делают отдельные SQL-запросы Hibernate, даже если они не выбраны (когда они выбраны, они Eager выбираются как соединение).

Я пробовал много разных вещей, таких как Hibernate Enhanced Bytecode и использование LazyInitilization, которое останавливает загрузку этих объектов - однако, когда они выбраны, кажется, что создается соединение и отдельный запрос выбора.

Hibernate: 
    /* select
        generatedAlias0 
    from
        OwningJob as generatedAlias0 
    where
        generatedAlias0.jobNumber=:param0 */ select
            owningjob0_.pkId as pkid1_13_0_,
            customer1_.customerPkId as customer1_0_1_,
            owningjob0_.customerCode as customer7_13_0_,
            ...
        from
            dbo.OwningJob owningjob0_ 
        left outer join
            dbo.Customer customer1_ 
                on owningjob0_.CustomerCode=customer1_.customerId 
        where
            owningjob0_.OwningJobId=?
Hibernate: 
    /* sequential select
        uk.co.essl.jobloadapi.model.jobapi.OwningJob */ select
            owningjob_.CustomerCode as customer7_13_ 
        from
            dbo.OwningJob owningjob_ 
        where
            owningjob_.pkId=?

Я не могу понять, почему делается этот дополнительный запрос на выбор, похоже, что Hibernate сбит с толку, потому что он уже был загружен с нетерпением?

Это тоже приводит к проблеме N + 1, любые идеи о том, что идет не так. Я открыт и для совершенно разных подходов!

Часть класса сущности:

@Entity
@Table(name = "OwningJob", schema = "dbo")
@GraphQLType(name = "Order", description = "Order description.")
@Getter
public class OwningJob {

  @ManyToOne
  @JoinColumn(name = "CustomerCode", referencedColumnName = "CustomerId")
  @LazyToOne(LazyToOneOption.NO_PROXY)
  @LazyGroup("owningJob_customer")
  public Customer customer;

}

Редактировать:

Теперь я попытался использовать API критериев JPA, и, похоже, у меня все еще такое же поведение:

    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Object> criteria =  builder.createQuery(Object.class);
    Root<OwningJob> root = criteria.from(OwningJob.class);

    Path<String> customerReference = root.get("customerReference");
    Path<String> jobNumber_ = root.get("jobNumber");
    Path<Object> customerPath = root.join("customer");

    criteria = criteria
        .multiselect(customerReference, jobNumber_, customerPath)
        .where(builder.equal(root.get("jobNumber"), jobNumber));

Это приводит к срабатыванию обоих этих запросов ...

Hibernate: 
    select
        owningjob0_.customerReference as col_0_0_,
        owningjob0_.OwningJobId as col_1_0_,
        customer1_.customerPkId as col_2_0_,
        customer1_.customerPkId as customer1_0_,
        customer1_.customerId as customer2_0_,
        customer1_.customerName as customer3_0_ 
    from
        dbo.OwningJob owningjob0_ 
    inner join
        dbo.Customer customer1_ 
            on owningjob0_.CustomerCode=customer1_.customerId 
    where
        owningjob0_.OwningJobId=?
Hibernate: 
    select
        customer0_.customerPkId as customer1_0_0_,
        customer0_.customerId as customer2_0_0_,
        customer0_.customerName as customer3_0_0_ 
    from
        dbo.Customer customer0_ 
    where
        customer0_.customerId=?

person jameslfc19    schedule 02.02.2021    source источник
comment
Вы пробовали @ManyToOne (fetch = FetchType.LAZY)? Я бы также рекомендовал использовать DTO вместо непосредственного аннотирования вашего класса сущности. Таким образом, у вас будет лучший контроль над тем, как ваша сущность создается и / или к ней осуществляется доступ. См. Мой ответ здесь: stackoverflow.com/a/58809449/10631518   -  person AllirionX    schedule 03.02.2021
comment
@AllirionX Да, я пробовал FetchType.LAZY, но SpringBoot все равно игнорирует их и использует EntityGraph. Кроме того, речь идет не о сериализации данных, она возвращает правильные значения, а просто извлекает ненужные данные путем выполнения дополнительных SQL-запросов. В идеале я хотел бы просто иметь возможность динамически выбирать только определенный набор значений для запроса. Итак, никакой ленивой инициализации, и если она не загружена в класс, просто верните ноль?   -  person jameslfc19    schedule 03.02.2021
comment
Ленивая загрузка позволяет загружать только то, к чему осуществляется доступ. Это единственное решение, которое я могу придумать для частичной загрузки объекта с помощью Hibernate, которое не подразумевает переопределения логики прокси-сервера гибернации. Очевидно, у него есть недостатки (например, несколько запросов к базе данных). Но, как вы, кажется, говорите, даже при ленивой загрузке данные все равно запрашиваются: проблема может исходить из Hibernate (загрузка, хотя она не должна) или из graphql-java (доступ к полю, когда он не должен, запускает ленивую загрузку). Отделение сериализации от сущности - простой способ определить, какая из двух библиотек дает сбой.   -  person AllirionX    schedule 03.02.2021
comment
При этом извлечение отношений ManyToOne и OneToOne не требует больших затрат (если база данных построена правильно), прирост производительности будет очень небольшим. Я лично счастлив загрузить все эти отношения, даже если graphql действительно нуждается в поле, и если у вас нет очень конкретной проблемы с производительностью, которую нужно решить, это очень приемлемое решение.   -  person AllirionX    schedule 03.02.2021
comment
@AllirionX, если вы проверите мою правку, я теперь попытался использовать критерии и получить такое же поведение? Несмотря на то, что я присоединяюсь к таблице Customer, она по-прежнему запускает еще один ненужный SQL-запрос ?!   -  person jameslfc19    schedule 03.02.2021
comment
Можете ли вы воспроизвести проблему за пределами graphql, в среде, где у вас будет полный контроль над сущностью (поскольку graphql-java может получить доступ к вашим полям без вашего ведома, и если вы не очень хорошо знаете библиотеку, это будет трудно узнать). Один из способов сделать это - использовать DTO между graphql и классом сущности, чтобы вы могли контролировать, когда сущность загружается / открывается. Цель здесь - определить, исходит ли проблема от Hibernate или от его прямого взаимодействия с graphql-java.   -  person AllirionX    schedule 04.02.2021
comment
@AllirionX Да, я получаю такое же поведение без GraphQL в отдельном проекте, это просто объекты ManyToOne и OneToOne, они отлично работают с OneToMany. Я попробую использовать DTO и посмотрю, как это работает!   -  person jameslfc19    schedule 04.02.2021
comment
Тогда проблема только в спящем режиме, и вы можете сосредоточиться на том, как сделать отложенную загрузку отношений OneToOne и ManyToOne. Вот такой вопрос: stackoverflow.com/questions/1444227/   -  person AllirionX    schedule 04.02.2021
comment
Вы также можете использовать Projection   -  person SSK    schedule 09.02.2021
comment
Этот пост baeldung всегда мне очень помогал. Имейте в виду, что даже ленивое загруженное отношение фактически загружается всякий раз, когда кто-либо (graphql-java, graphql-spqr, ..) вызывает метод получения для этого поля. baeldung.com/hibernate-lazy-eager-loading   -  person Robert    schedule 05.03.2021