英文:
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
// 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 = "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
答案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<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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论