yahoo / elide

Elide is a Java library that lets you stand up a GraphQL/JSON-API web service with minimal effort.

Home Page:https://elide.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot traverse more than one level up a graph

lewimuchiri opened this issue · comments

Any attempt to fetch a collection upwards by more than one level doesn't bring expected results.
For example, GrandChild -> Child -> Parent should generate such a query:

select * from GrandChild g
left join Child c on g.parent_id = c.id
left join Parent p on c.parent_id = p.id

But Elide only generates a query that reaches up to child:

select * from GrandChild g
left join Child c on g.parent_id = c.id

And the Parent collection is left as null. Is this a bug/missing feature?

Can you provide the query and models?

//ArtifactGroup.java
@Include(name = "group")
@Table(name = "artifactgroup")
@Entity
@Subscription
@Data
public class ArtifactGroup {
    @Id
    private String name = "";

    @SubscriptionField
    private String commonName = "";

    @SubscriptionField
    private String description = "";

    @SubscriptionField
    @OneToMany(mappedBy = "group")
    private List<ArtifactProduct> products = new ArrayList<>();
}

//ArtifactProduct.java
@Include(rootLevel = false, name = "product")
@Table(name = "artifactproduct")
@Entity
public class ArtifactProduct {
    @Id
    private String name = "";

    private String commonName = "";

    private String description = "";

    @ManyToOne
    private ArtifactGroup group = null;

    @OneToMany(mappedBy = "artifact")
    private List<ArtifactVersion> versions = new ArrayList<>();
}

//ArtifactVersion.java
@Include(rootLevel = true, name = "version")
@Table(name = "artifactversion")
@Entity
public class ArtifactVersion {
    @Id
    private String name = "";

    private Date createdAt = new Date();

    @ManyToOne
    private ArtifactProduct artifact;
}

I have slightly modified the Spring Boot Example Project to demonstrate this. Please note that rootLevel = true for ArtifactVersion. I've done this intentionally so that I can query from ArtifactVersion directly (ArtifactVersion is the "grand child" in this relationship).

{
  version {
    edges {
      node {
        artifact {
          edges {
            node {
              group {
                edges {
                  node {
                    name
                    commonName
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Here is the generated SQL statement (note that there is no join statement for group, and no subsequent queries are sent after this one):

select a1_0.name,a2_0.name,a2_0.commonName,a2_0.description,a2_0.group_name,a1_0.createdAt from artifactversion a1_0 left join artifactproduct a2_0 on a2_0.name=a1_0.artifact_name offset ? rows fetch first ? rows only

Thanks for the example query and models. It was quite helpful.

I can reproduce this issue on the example project.

However it appears to be a Hibernate issue. Upgrading minimally to 6.2.0 seems to fix it.

Since Spring Boot only does minor version upgrades inline with their own minor version upgrades you will likely need to override the Hibernate version or wait for Spring Boot 3.1.0.

You can override using the following in the pom.xml.

    <properties>
        <hibernate.version>6.2.2.Final</hibernate.version>
    </properties>

This is what Hibernate generates after upgrading.

Hibernate: select a1_0.name,a2_0.name,a2_0.commonName,a2_0.description,a2_0.group_name,a1_0.createdAt from artifactversion a1_0 left join artifactproduct a2_0 on a2_0.name=a1_0.artifact_name offset ? rows fetch first ? rows only
Hibernate: select a1_0.name,a1_0.commonName,a1_0.description from artifactgroup a1_0 where array_contains(?,a1_0.name)

For some reason this still isn't working for me. I've updated my build.gradle.kts as follows but still the grandparent entity is not being fetched:

    implementation("com.yahoo.elide:elide-spring-boot-starter:7.0.0-pr4") {
        exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
    }
    constraints {
        implementation("org.hibernate.orm:hibernate-core:6.2.2.Final") {
            because("Elide brings in String Boot Data JPA, which also brings in " +
                    "hibernate-core, but version 6.1.7.Final. " +
                    "We're upgrading to version 6.2.2.Final until Elide can upgrade to Spring Boot 3.1.0 " +
                    "because of this issue: https://github.com/yahoo/elide/issues/2951#issuecomment-1534149880")
        }
    }

Does gradle dependencies reflect that Hibernate is the latest?

You can try the branch at https://github.com/justin-tay/elide-spring-boot-example/tree/gh2951_elide-7.x with your query.

The example works. Let me find out what's wrong with my project. Thanks

@justin-tay @aklish after much debugging, I discovered that the reason it works for the Example Project with Hibernate 6.2.2.Final is because the entities in the Example Project are all being Eager-fetched. But if your entities are Lazy Fetched, then it still will not work. Elide only includes the immediate parent in the entity projection, so a grand-parent will not be fetched if it has the FetchType.LAZY annotation on it.

I usually use Lazy fetching because I don't always want related entities to be Eager-fetched. Is there another way to solve this apart from using FetchType.EAGER on the grand-parent entity?

I'm not sure but are you using property accessors? You may need to do so if you are using FetchType.LAZY.

You can try the updated code at https://github.com/justin-tay/elide-spring-boot-example/tree/gh2951_elide-7.x.

I was not using property accessors, but I have tested with that approach and it now works. Thank you for your support. I had used @ComputedAttribute as a workaround to fetch the grand-parent entity but now I know how to modify my code in case the client applications need this often.
If there is no better solution then we can close this issue