使用JPA Criteria API,您能否执行仅产生一个连接的fetch join操作?

33

使用JPA 2.0。默认情况下(没有显式的fetch设置),@OneToOne(fetch = FetchType.EAGER)字段会以1+N查询方式获取,其中N是包含定义到不同关联实体的实体数。使用Criteria API,可以尝试按如下方式避免:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> query = builder.createQuery(MyEntity.class);
Root<MyEntity> root = query.from(MyEntity.class);
Join<MyEntity, RelatedEntity> join = root.join("relatedEntity");
root.fetch("relatedEntity");
query.select(root).where(builder.equals(join.get("id"), 3));

理想情况下,上述内容应与以下内容等效:

SELECT m FROM MyEntity m JOIN FETCH myEntity.relatedEntity r WHERE r.id = 3

然而,基于条件的查询会导致根表与相关实体表不必要地重复连接两次;一次用于提取数据(fetch),一次用于where谓词。生成的SQL大致如下:

然而,对于根表和关联实体表的条件查询结果,存在无需重复连接的情况;一次用于获取(fetch),一次用于where谓词。生成的SQL语句类似于下面这样:

SELECT myentity.id, myentity.attribute, relatedentity2.id, relatedentity2.attribute 
FROM my_entity myentity 
INNER JOIN related_entity relatedentity1 ON myentity.related_id = relatedentity1.id 
INNER JOIN related_entity relatedentity2 ON myentity.related_id = relatedentity2.id 
WHERE relatedentity1.id = 3

唉,如果我只是做了fetch操作,那么我就没有表达式可在where子句中使用。

我有所遗漏,还是这是Criteria API的限制?如果是后者,在JPA 2.1中是否得到了纠正或是否有任何特定于供应商的增强措施?

否则,放弃编译时类型检查似乎更好(我意识到我的示例未使用元模型),并使用动态JPQL TypedQueries。

3个回答

39

与其使用root.join(...),您可以使用返回Fetch<>对象的root.fetch(...)

Fetch<>Join<>的后代,但可以以类似的方式使用。

您只需要将Fetch<>转换为Join<>,它应该适用于EclipseLink和Hibernate

...
Join<MyEntity, RelatedEntity> join = (Join<MyEntity, RelatedEntity>)root.fetch("relatedEntity");
...

2
Fetch既不是Join也不是Expression的子接口,所以我不能将其分配给Join,也不能在where子句中使用它(例如)。参考:http://docs.oracle.com/javaee/6/api/javax/persistence/criteria/Fetch.html那么,这怎么可能呢?这是提供程序特定的吗? - Shaun
2
抱歉,您应该进行类型转换。按照这里描述的方式,它至少应该在Hybernate中起作用http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/querycriteria.html#querycriteria-from-fetch 我已经修正了我的答案。 - Ondrej Bozek
1
这对我有用,但将其转换为Join<MyEntity, RelatedEntity>会导致我的IDE标记该语句为警告。 我发现将其转换为org.hibernate.ejb.criteria.path.SingularAttributeJoin<MyEntity, RelatedEntity>可以消除警告。调用root.fetch执行以下操作: FetchParent#fetch(String) AbstractFromImpl#fetch(String) [FetchParent的实现] AbstractFromImpl#fetch(SingularAttribute, JoinType) AbstractFromImpl#constructJoin(SingularAttribute,JoinType) [返回SingularAttributeJoin] - Chris Parton
1
这是一个有点老的话题,但也许你会知道:虽然可以从Fetch转换为Join,但不能执行fetch.on(...),因为它会说“无法与获取联接一起使用with子句-请使用过滤器”。我需要添加一个额外的条件来加入(除了ID之外)。当我只是做root.join(...),然后join.on(...)时,正确的连接被生成,但它们没有获取已连接的数据(在选择中未列出字段)。也许你知道一些解决方法? - Shadov
1
@ChrisParton 将其类型强制转换为 (Join),让你的集成开发环境感到愉快! - raghavsood33
显示剩余3条评论

11

从 JPA 2.1 开始,您可以使用动态实体图来实现。删除您的提取(fetch)并按如下指定实体图:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> query = builder.createQuery(MyEntity.class);
Root<MyEntity> root = query.from(MyEntity.class);
Join<MyEntity, RelatedEntity> join = root.join("relatedEntity");
query.select(root).where(builder.equal(join.get("id"), 3));
EntityGraph<MyEntity> fetchGraph = entityManager.createEntityGraph(MyEntity.class);
fetchGraph.addSubgraph("relatedEntity");
entityManager.createQuery(query).setHint("javax.persistence.loadgraph", fetchGraph);

CriteriaBuilder 中正确的方法是 equal 而不是 equals。 - Kronen

1

在EclipseLink上使用root.fetch()会创建一个带有INNER JOIN的SQL语句,因为有3种类型,默认为INNER。

INNER,  LEFT,  RIGHT;

建议使用 CreateQuery。
TypedQuery<T> typedQuery = entityManager.createQuery(query);

编辑:您可以像这样将根转换为 From:
From<?, ?> join = (From<?, ?>) root.fetch("relatedEntity");

1
这个问题涉及到CriteriaQuery,它没有setHint方法。 - Karl Kildén
2
当您使用Criteria API构建查询时,您不会放弃整个代码,而是使用TypedQuery构建查询,以便设置提示。请参阅问题标题中的“使用JPA Criteria API”。您也可以建议如何仅使用SQL完成此操作。 - Karl Kildén
阅读此API https://docs.oracle.com/javaee/6/api/javax/persistence/criteria/CriteriaQuery.html你看到setHint了吗? - Karl Kildén
1
这个问题与查询接口无关,而是与未实现该接口的CriteriaQuery有关。你还可以建议他使用一些SQL。请再次阅读问题和我的评论。 - Karl Kildén
伙计,那只是一个建议。我会把它删除,好吗? - Fábio Almeida
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接