1

I'm currently using GraphQL SPQR with Spring Boot. In order to improve database performance I have written custom logic using the @GraphQLEnvironment ResolutionEnvironment from this I have been able to find all the requested values and filter them for the @Entity annotation, I have then used these to create a dynamic EntityGraph that is used to Eager fetch those specific values.

Now, this seems to work for Entities mapped as @OneToMany however my @ManyToOne and @OneToOne Entities still make separate Hibernate SQL queries even if they are not selected (When they are selected they are Eager fetched as a Join).

I have tried lots of different things such as Hibernate Enhanced Bytecode and using LazyInitilization which does stop those entities being loaded - however when they are selected it seems to make a Join and a separate select query too.

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=?

I can't figure out why that extra select request is being made, it seems like Hibernate is confused because it was already Eagerly loaded?

This leads to the N+1 problem too, any ideas on what's going wrong. I'm open to completely different approaches too!

Part of Entity Class:

@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;

}

Edit:

I have now tried using the JPA Criteria API and I still seem to get the same behaviour:

    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));

This results in both these Queries firing...

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=?
jameslfc19
  • 832
  • 1
  • 7
  • 14
  • Have you tried '@ManyToOne(fetch = FetchType.LAZY)' ? I would also recommend using DTO instead of directly annotating your entity class. That way you would have better control on how your entity is created and/or accessed. See my response here: https://stackoverflow.com/a/58809449/10631518 – AllirionX Feb 03 '21 at 01:56
  • @AllirionX Yeah I have tried FetchType.LAZY but SpringBoot seems to ignore those anyway and uses EntityGraph's. Also this isn't about the serialization of the data, It returns the correct values, but it is just fetching unneeded data by making additional SQL queries. Ideally I would like to just be able to dynamically select only a specific set of values to be queried. So no Lazy initialisation at all and if it isn't loaded into the class just return null? – jameslfc19 Feb 03 '21 at 10:00
  • Lazy loading allow to only load what is being accessed. This is the only solution I can think of to partially load an entity with Hibernate that does not imply overriding the hibernate proxy logic. Obviously it has drawbacks (like multiple database queries). But as you seem to say, even with lazy loading the data get queried anyway: the problem can come from Hibernate (loading while it should not) or from graphql-java (accessing the field when it should not, triggering the lazy loading). Separating serialization from the entity is an easy way to identify which of the two libraries is failing – AllirionX Feb 03 '21 at 11:26
  • That being said, ManyToOne and OneToOne relationship are not costly to retrieve (if the database is built properly), the performance gain would be very very small. I am personally happy to load all those relationships even if graphql does need the field, and unless you got a very specific performance problem to solve that is a very acceptable solution. – AllirionX Feb 03 '21 at 11:30
  • @AllirionX if you check my edit I have now tried using the Criteria and get the same behaviour? Even though I am joining the Customer table it's still firing off another unneeded SQL query?! – jameslfc19 Feb 03 '21 at 12:31
  • Can you reproduce the problem outside of graphql, in an environment where you would have complete control over the entity (as graphql-java can access your fields without you knowing and unless you know the library very well it's gonna be hard to find out). One way of doing that is to use a DTO between graphql and the entity class so you can control when the entity is loaded/accessed. The goal here is to identify if the problem comes from Hibernate or from its direct interaction with graphql-java. – AllirionX Feb 03 '21 at 22:21
  • @AllirionX Yes I get the same behaviour without GraphQL in a separate project, it's just ManyToOne and OneToOne Entities too, it works perfectly fine with OneToMany. I will try using a DTO and see how that works! – jameslfc19 Feb 04 '21 at 10:43
  • Then the problem is only with hibernate, and you can focus on how to make OneToOne and ManyToOne relationship lazy load. Here is a SO question about it: https://stackoverflow.com/questions/1444227/how-can-i-make-a-jpa-onetoone-relation-lazy – AllirionX Feb 04 '21 at 12:25
  • You can use the Projection as well – SSK Feb 09 '21 at 07:23
  • This baeldung post always helped me a lot. Keep in mind, that even a lazy loaded relation is actually loaded whenever "anyone" (graphql-java, graphql-spqr, ..) calls the getter for that field. https://www.baeldung.com/hibernate-lazy-eager-loading – Robert Mar 05 '21 at 08:35

0 Answers0