Benjamin Maurer
Benjamin Maurer

Reputation: 3753

JPA select association and use NamedEntityGraph

Our in-house framework built with Java 11, Spring Boot, Hibernate 5 and QueryDSL does a lot of auto-generation of queries. I try to keep everything efficient and load associations only when needed. When loading full entities, the programmer can declare a NamedEntityGraph to be used. Now there is one case where a query like this is generated:

select user.groups
from User user
where user.id = ?1

Where the Entities in question look like this:

@Entity
@NamedEntityGraph(name = User.ENTITY_GRAPH,
    attributeNodes = {
        @NamedAttributeNode(User.Fields.permissions),
        @NamedAttributeNode(value = User.Fields.groups, subgraph = "user-groups-subgraph")
    },
    subgraphs = @NamedSubgraph(
        name = "user-groups-subgraph",
        attributeNodes = {
            @NamedAttributeNode(Group.Fields.permissions)
      }
    ))
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  
  @Enumerated(EnumType.STRING)
  @ElementCollection(targetClass = Permission.class)
  @CollectionTable(name = "USERS_PERMISSIONS", joinColumns = @JoinColumn(name = "uid"))
  private Set<Permission> permissions = EnumSet.of(Permission.ROLE_USER);
  
  @ManyToMany(fetch = LAZY)
  private Set<Group> groups = new HashSet<>();
}


@Entity
@NamedEntityGraph(name = Group.ENTITY_GRAPH,
    attributeNodes = {
        @NamedAttributeNode(value = Group.Fields.permissions)
    })
public class Group {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  private Long id;
  
  @Enumerated(EnumType.STRING)
  @ElementCollection(targetClass = Permission.class)
  @CollectionTable(
      name = "GROUPS_PERMISSIONS",
      joinColumns = @JoinColumn(name = "gid")
  )
  @NonNull
  private Set<Permission> permissions = EnumSet.noneOf(Permission.class);
}

When selecting either User or Group directly, the generated query simply applies the provided NamedEntityGraphs. But for the above query the exception is:

org.hibernate.QueryException:
  query specified join fetching, but the owner of the fetched association was not present in the select list
  [FromElement{explicit,collection join,fetch join,fetch non-lazy properties,classAlias=user,role=foo.bar.User.permissions,tableName={none},tableAlias=permission3_,origin=null,columns={,className=null}}]

I first tried the User graph, but since we are fetching Groups, I tried the Group graph. Same Exception.

Problem is, there is no easy way to add a FETCH JOIN to the generated query, since I don't know which properties of the association should be joined in anyway. I would have to load the Entitygraph, walk it and any subgraph and generated the right join clauses.

Some more details on Query generation:

// QueryDsl 4.3.x Expressions, where propType=Group.class, entityPath=User, assocProperty=groups
final Path<?> expression = Expressions.path(propType, entityPath, assocProperty);

// user.id = ?1
final BooleanExpression predicate = Expressions.predicate(Ops.EQ, idPath, Expressions.constant(rootId));

// QuerydslJpaPredicateExecutor#createQuery from Spring Data JPA
final JPQLQuery<P> query = createQuery(predicate).select(expression).from(path);

// Add Fetch Graph
((AbstractJPAQuery<?, ?>) query).setHint(GraphSemantic.FETCH.getJpaHintName(), entityManager.getEntityGraph(fetchGraph));

EDIT:

I can reproduce this with a simple JPQL Query. It's very strange, if I try to make a typed query, it will select a List of Sets of Group and untyped just a List of Group. Maybe there is something conceptually wrong - I'm selecting a Collection and I'm trying to apply a fetch join on it. But JPQL doesn't allow a SELECT from a subquery, so I'm not sure what to change..

// em is EntityManager
List gs = em
                .createQuery("SELECT u.groups FROM User u WHERE u.id = ?1")
                .setParameter(1, user.getId())
                .setHint(GraphSemantic.FETCH.getJpaHintName(), em.getEntityGraph(Group.ENTITY_GRAPH))
                .getResultList();

Same Exception:

org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list

Upvotes: 1

Views: 1591

Answers (1)

Benjamin Maurer
Benjamin Maurer

Reputation: 3753

So the problem can be distilled down to a resolution problem of the Entit Graphs attributes:

select user.groups
from User user
where user.id = ?1

With the Entity Graph

EntityGraph<Group> eg = em.createEntityGraph(Group.class);
eg.addAttributeNodes(Group.Fields.permissions);

Gives an Exception that shows that Hibernate tries to fetch User.permissions instead of Group.permissions. This is the bug report.

And there is another bug regarding the use of @ElementCollection here.

Upvotes: 1

Related Questions