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.
的使用还有另一个错误
我们使用 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.