如何正确表达JPQL“join fetch”和“where”子句为JPA 2 CriteriaQuery?

时间:2011-04-28 09:23:48

标签: java jpa criteria jpa-2.0

考虑以下JPQL查询:

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?

3 个答案:

答案 0 :(得分:64)

在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。

答案 1 :(得分:7)

我将使用James答案中的一个很好的例子并添加替代解决方案来直观地显示问题。

在执行以下查询时,没有FETCH

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

您将如预期的那样从Employee获得以下结果:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613
1          | James        | 6       | 416

但是当您在FETCH上添加JOIN一词时,会发生以下情况:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613

这两个查询生成的SQL是相同的,但是当您在416联接上使用WHERE时,Hibernate会删除FETCH寄存器上的内存

因此,要使所有电话 正确应用WHERE,您需要拥有两个JOIN:一个用于WHERE,另一个用于FETCH。喜欢:

Select e from Employee e 
join e.phones p 
join fetch e.phones      //no alias, to not commit the mistake
where p.areaCode = '613'

答案 2 :(得分:0)

我可能会迟到,但从我的角度来看。

Select e from Employee e 
join e.phones p 
join fetch e.phones      //no alias, to not commit the mistake
where p.areaCode = '613'

这可以转换为以下 SQL 查询

Select e.id, e.name, p.id ,p.phone
From Employe e
inner join Phone p on e.id = p.emp_id
where exists(
  select 1 from Phone where Phone.id= p.id and Phone.area ='XXX'  
)

这将获取属于某个区域的员工的所有电话。

但是

Select e from Employee e 
join fetch e.phones p      //no alias, to not commit the mistake
where p.areaCode = '613'

可以翻译成以下 SQL 查询

Select  e.id, e.name, p.id ,p.phone
From    Employe e
inner   join Phone p on e.id = p.id
Where   p.area ='XXX'  

Select e.id, e.name, p.id ,p.phone
From Employe e
inner join Phone p on e.id = p.emp_id and p.area ='XXX'  

这会将行选择限制为员工电话位于 XXX 区域的行

终于写到这里了

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

可以看作

Select e.id, e.name 
from Employe e
where exists (
 select 1 from phone p where p.emp_id = e.id and p.area = 'XXX'
)

我们只获取在某些地区有电话号码的员工数据

这应该有助于在每次查询后获得想法。