JPA select 关联和使用 NamedEntityGraph

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

当 select 直接访问用户或组时,生成的查询仅应用提供的 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 图,但由于我们正在获取组,所以我尝试了 Group 图。同样的例外。

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

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

// 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 查询重现它。这很奇怪,如果我尝试进行类型化查询,它将 select 一个组集列表,而只是一个组列表。 也许概念上有问题 - 我正在 select 创建一个集合,我正在尝试对其应用提取连接。但是 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: query specified join fetching, but the owner of the fetched association was not present in the select list

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

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。这是 bug report.

关于 @ElementCollection here.

的使用还有另一个错误