2011-04-28 23 views
34

考虑以下JPQL查询:如何正确地将JPQL“join fetch”与“where”子句一起表示为JPA 2 CriteriaQuery?

SELECT foo FROM Foo foo 
INNER JOIN FETCH foo.bar bar 
WHERE bar.baz = :baz 

我想翻译成Critieria查询此。这是据我已经得到了:

CriteriaBuilder cb = em.getCriteriaBuilder(); 
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class); 
Root<Foo> r = cq.from(Foo.class); 
Fetch<Foo, Bar> fetch = r.fetch(Foo_.bar, JoinType.INNER); 
Join<Foo, Bar> join = r.join(Foo_.bar, JoinType.INNER); 
cq.where(cb.equal(join.get(Bar_.baz), value); 

这里的明显的问题是,我做同样加入两次,因为Fetch<Foo, Bar>似乎并不有一个方法来获得一个Path。 有没有办法避免必须加入两次?或者我必须坚持使用旧的JPQL,查询就像这么简单?

+1

嗯,谢谢,但我宁愿坚持使用标准的API,并尽量避免额外的第三方库。如果我想要做的事对JPA Criteria API来说是不可能的,那么我可能会坚持使用普通的JPQL。 – chris 2011-04-28 09:40:05

+0

你解决了你的问题吗?我有同样的问题。 Fetch无法投射到Join,并且我无法从Fetch获取路径。它几乎不可用。唯一的解决办法是有两个相同的连接,这是不可接受的。 – svlada 2015-04-22 07:17:16

+0

好吧,詹姆斯的回答很好地描述了问题的根源。你不能这样做,这是一个健全的设计决策。如果我没有记错的话,我最终加入了两次。这就是说,如果我有选择,我将永远不会再使用JPA,因为我认为这是一个无用的抽象层,在阉割底层实现时会增加不必要的复杂性。 – chris 2015-04-22 12:22:26

回答

52

在JPQL中,规范中实际上也是如此。 JPA规范不允许将别名赋予获取联接。问题在于,通过限制联合抓取的上下文,您可以轻松地将自己拍摄在脚下。加入两次更安全。

这通常是ToMany比ToOnes更多的问题。 例如,

Select e from Employee e 
join fetch e.phones p 
where p.areaCode = '613' 

这将错误返回包含在“613”区号电话号码,但会漏掉其他地区的电话号码,在返回的列表中的所有员工。这意味着在613和416区号中有电话的员工将丢失416电话号码,因此该对象将被损坏。如果你知道你在做什么,额外的连接是不可取的,一些JPA提供者可能允许取消连接的别名,并且可以允许将Criteria Fetch转换为Join。

+2

明智的答案,谢谢。没有想到这一点。 Hibernate从来没有向我抱怨关于别名加入的别名,我不知道这实际上违反了规范。 – chris 2011-04-28 14:17:57

+4

另请参阅http://java-persistence-performance.blogspot.com/2012/04/objects-vs-data-and-filtering-join.html – James 2012-04-24 12:34:21