[英]How to properly express JPQL “join fetch” with “where” clause as JPA 2 CriteriaQuery?
考虑以下 JPQL 查询:
SELECT foo FROM Foo foo
INNER JOIN FETCH foo.bar bar
WHERE bar.baz = :baz
我正在尝试将其转换为 Criteria 查询。 据我所知,这是:
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?
在 JPQL 中,规范中实际上也是如此。 JPA 规范不允许为获取连接指定别名。 问题是您可以通过限制连接获取的上下文轻松地用这个来打自己的脚。 加入两次更安全。
这通常是 ToMany 的问题,而不是 ToOnes。 例如,
Select e from Employee e
join fetch e.phones p
where p.areaCode = '613'
这将错误地返回所有在“613”区号中包含数字的员工,但会在返回的列表中遗漏其他地区的电话号码。 这意味着拥有 613 和 416 区号电话的员工将丢失 416 电话号码,因此 object 将被损坏。
当然,如果您知道自己在做什么,那么额外的连接是不可取的,一些 JPA 提供程序可能允许别名连接提取,并且可能允许将标准提取转换为连接。
我将使用 James answer 中的出色示例并添加替代解决方案来直观地展示问题。
当您执行以下查询时,没有FETCH
:
Select e from Employee e
join e.phones p
where p.areaCode = '613'
如您所料,您将获得来自Employee
的以下结果:
员工ID | 员工姓名 | 电话号码 | 电话区号 |
---|---|---|---|
1 | 詹姆士 | 5 | 613 |
1 | 詹姆士 | 6 | 416 |
但是当您在JOIN
上添加FETCH
字时,会发生以下情况:
员工ID | 员工姓名 | 电话号码 | 电话区号 |
---|---|---|---|
1 | 詹姆士 | 5 | 613 |
生成的 SQL 对于两个查询是相同的,但是 Hibernate 删除了 memory 上的416
寄存器,当您在FETCH
上使用WHERE
时。
因此,要带上所有电话并正确应用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'
我可能会迟到回答这个问题,但从我的角度来看。
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'
)
我们只获取在某些区域有电话号码的员工数据
这应该有助于在每次查询后得到这个想法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.