Search code examples
jpajoinmany-to-manyquerydsl

How to join fetch an attribute of element in collection with QueryDSL JPA


Entities as below:

class A {
    Long id;

    @ManyToMany
    @JoinTable(name = "rel_a_b", joinColumns = @JoinColumn(name = "a_id"), inverseJoinColumns = @JoinColumn(name = "b_id"))
    Set<B> bSet;
}

class B {
    Long id;

    @ManyToMany(mappedBy = "bSet")
    Set<A> aSet;

    @ManyToMany
    @JoinTable(name = "rel_b_c", joinColumns = @JoinColumn(name = "b_id"), inverseJoinColumns = @JoinColumn(name = "c_id"))
    Set<C> cSet;
}

class C {
    Long id;

    @ManyToMany(mappedBy = "cSet")
    Set<B> bSet;
}

I need to select A entities and join fetch bSet and cSet in B entity. Using JPA Criteria, codes as below:

    final Fetch<A, B> bSetFetch = rootA.fetch("bSet", JoinType.LEFT);
    bSetFetch.fetch("cSet", JoinType.LEFT);

are working perfectly, but I can't achieve this with QueryDSL. I tried

    final QA a = QA.a;
    jpaQuery
    .from(a)
    .leftJoin(a.bSet, QB.b).fetchJoin()
    .leftJoin(QB.b.cSet).fetchJoin()
    .select(a)

but it throws exception that

query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=b,role=A.b,tableName=`b`,tableAlias=b4_,origin=a a2_,columns={a2_.id ,className=B}}] [select a
from A a
  left join fetch a.bSet as b
  left join fetch b.cSet]]

, if without fetchJoin(), the results don't include bSet and cSet. Could anyone solve this?


Solution

  • The fetch joins are applied correctly from a QueryDSL point of view. We can also observe this from the fact that the produced JPQL query looks correct.

    The limitation here is that Hibernate only allows FETCH JOINS if the owner of the fetch association is projected in the select clause. cSet is an association on B, so you would need to project your b's or omit the fetch join for cSet. For example:

    jpaQuery
        .from(a)
        .leftJoin(a.bSet, QB.b).fetchJoin()
        .leftJoin(QB.b.cSet).fetchJoin()
        .select(a, b)
    

    Now this will result in duplicate results for a due to the cardinality of bSet. That is just a limitation of fetch joins in Hibernate.

    Alternatively, you could consider specifying a fetch graph for the query:

    EntityGraph postGraph = em.getEntityGraph("post");
    query.setHint("javax.persistence.fetchgraph", postGraph);
    

    For more information on using EntityGraphs see https://www.baeldung.com/jpa-entity-graph