考虑以下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?
答案 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'
)
我们只获取在某些地区有电话号码的员工数据
这应该有助于在每次查询后获得想法。