JPA选择关联并使用NamedEntityGraph。

huangapple go评论82阅读模式
英文:

JPA select association and use NamedEntityGraph

问题

我们的内部框架是使用Java 11、Spring Boot、Hibernate 5和QueryDSL构建的,可以自动生成大量查询。我尽量保持一切高效,只在需要时加载关联。

当加载完整实体时,程序员可以声明要使用的NamedEntityGraph。现在有一种情况会生成像这样的查询:

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

涉及到的实体如下所示:

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

直接选择User或Group时,生成的查询只是应用提供的NamedEntityGraphs。但对于上述查询,异常是:

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}}]

我首先尝试了User图,但由于我们正在获取Groups,所以我尝试了Group图。同样的异常。

问题是,没有简单的方法来添加FETCH JOIN到生成的查询,因为我不知道关联的哪些属性应该被联接。我需要加载Entitygraph,遍历它以及任何子图,并生成正确的联接子句。

关于查询生成的更多细节:

// QueryDsl 4.3.x表达式,其中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));

// 从Spring Data JPA创建查询的QuerydslJpaPredicateExecutor#createQuery
final JPQLQuery<P> query = createQuery(predicate).select(expression).from(path);

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

编辑:

我可以使用一个简单的JPQL查询重现这个问题。非常奇怪的是,如果我尝试创建一个类型化查询,它会选择一个Set的List,而不是一个不带类型的List。也许在概念上有一些问题 - 我选择了一个集合,然后尝试在它上应用fetch join。但是JPQL不允许从子查询中进行选择,所以我不知道该如何更改。

// em是EntityManager
List<Group> groups = 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();

同样的异常:

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

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 = &quot;user-groups-subgraph&quot;)
    },
    subgraphs = @NamedSubgraph(
        name = &quot;user-groups-subgraph&quot;,
        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 = &quot;USERS_PERMISSIONS&quot;, joinColumns = @JoinColumn(name = &quot;uid&quot;))
  private Set&lt;Permission&gt; permissions = EnumSet.of(Permission.ROLE_USER);
  
  @ManyToMany(fetch = LAZY)
  private Set&lt;Group&gt; groups = new HashSet&lt;&gt;();
}


@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 = &quot;GROUPS_PERMISSIONS&quot;,
      joinColumns = @JoinColumn(name = &quot;gid&quot;)
  )
  @NonNull
  private Set&lt;Permission&gt; 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&lt;?&gt; 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&lt;P&gt; query = createQuery(predicate).select(expression).from(path);

// Add Fetch Graph
((AbstractJPAQuery&lt;?, ?&gt;) 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(&quot;SELECT u.groups FROM User u WHERE u.id = ?1&quot;)
				.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

答案1

得分: 1

所以问题可以简化为实体图属性的分辨率问题:

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

使用实体图:

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

会导致异常,显示 Hibernate 尝试获取 User.permissions 而不是 Group.permissions。这是错误报告

还有另一个关于使用 @ElementCollection 的错误在这里

英文:

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&lt;Group&gt; 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.

huangapple
  • 本文由 发表于 2020年7月21日 21:45:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/63015923.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定