首页 > 解决方案 > JPA 选择关联并使用 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);
}

直接选择用户或组时,生成的查询仅应用提供的 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}}]

我首先尝试了用户图,但由于我们正在获取组,所以我尝试了组图。同样的例外。

问题是,没有简单的方法可以将 a 添加FETCH JOIN到生成的查询中,因为我不知道应该加入关联的哪些属性。我将不得不加载实体图,遍历它和任何子图并生成正确的连接子句。

有关查询生成的更多详细信息:

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

编辑:

我可以通过一个简单的 JPQL 查询来重现这一点。很奇怪,如果我尝试进行类型化查询,它会选择一个 List of Sets of Group 而未键入的只是一个 List of Group。也许在概念上有些错误 - 我正在选择一个 Collection 并且我正在尝试对其应用 fetch join。但是 JPQL 不允许从子查询中进行 SELECT,所以我不确定要更改什么..

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

相同的例外:

org.hibernate.QueryException:查询指定连接提取,但提取关联的所有者不在选择列表中

标签: javaspring-boothibernatejpaquerydsl

解决方案


所以这个问题可以归结为实体图属性的解析问题:

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 here的错误。


推荐阅读