【问题标题】:JPA select association and use NamedEntityGraphJPA 选择关联并使用 NamedEntityGraph
【发布时间】:2020-11-10 22:00:15
【问题描述】:

我们使用 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}}]

我首先尝试了 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 查询来重现这一点。很奇怪,如果我尝试进行类型化查询,它会选择一个 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: 查询指定连接提取,但提取关联的所有者不在选择列表中

【问题讨论】:

  • 我怀疑 sn-p 生成提到的 JPQL 片段,因为异常特别提到存在不受支持的连接,并且您预期的查询没有连接。请提供完整的查询生成代码和实际生成的 JPQL 查询。此外,QueryDSL 不能开箱即用地处理 EntityGraphs。您目前如何将要与查询一起使用的 EntityGraph 关联起来?
  • 所以我添加了设置 Fetch Graph。它确实隐藏了一点,但您可以设置获取提示。 join不在实际查询中,而是由Entity Graph定义(Fetch graphs相当于fetch joins)。我能够使用简单的 JPQL 查询通过测试用例重现该行为。我想这里在概念上有些错误——就像我实际上想将获取图应用于集合的成员一样——但我无法弄清楚。
  • 可能是它试图加入user.groups.permissions,这是不允许的。您可以更改查询以使其投射SELECT g FROM User user JOIN user.groups g 吗? (如果正确应用了获取计划,FETCH JOIN g.permissions 仍应添加到查询中)。我知道它可以为 QueryDSL 查询指定一个获取计划,它只是没有得到官方支持并且很难通过 API 来实现 - 所以我主要想知道你是否以正确的方式做到了。
  • 所以我已经尝试了你的建议,但我又得到了异常。查看详细信息,我意识到这次它尝试加载 User.permissions。也许实体图仅应用于根实体(用户)并且属性恰好具有相同的名称......显式编写SELECT g FROM User u JOIN u.groups g LEFT JOIN FETCH g.permissions WHERE u.id = ?1似乎有效。
  • 也许获取计划天真地假设它应用于第一个连接节点。如果您坚持使用 fetch 计划,您可以尝试将查询重写为:SELECT g FROM user.groups g 甚至SELECT g FROM Group g WHERE g.user =...(尽管在这里您可能必须根据您的查询类型声明组类的反比关系)

标签: java spring-boot hibernate jpa querydsl


【解决方案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。这是bug report

还有一个关于使用@ElementCollectionhere的错误。

【讨论】:

    猜你喜欢
    • 2021-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-30
    • 2015-08-09
    • 2014-08-25
    • 1970-01-01
    相关资源
    最近更新 更多