如何正确地将带有“where”子句的 JPQL“join fetch”表达为 JPA 2 CriteriaQuery?

问题描述 投票:0回答:3

考虑以下 JPQL 查询:

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

我正在尝试将其转换为条件查询。据我所知,这是:

var cb = em.getCriteriaBuilder();
var query = cb.createQuery(Foo.class);
var foo = query.from(Foo.class);
var fetch = foo.fetch(Foo_.bar, JoinType.INNER);
var join = foo.join(Foo_.bar, JoinType.INNER);
query.where(cb.equal(join.get(Bar_.baz), value);

这里明显的问题是我做了两次相同的连接,因为

Fetch
类似乎没有获取
Path
的方法。 有什么办法可以避免加入两次吗?或者我是否必须坚持使用良好的旧式 JPQL 并进行如此简单的查询?

java jpa criteria jpa-2.0
3个回答
97
投票

在 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 强制转换为联接。


37
投票

当您执行以下查询时,没有

FETCH

:

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

您将从 
Employee

获得以下结果,正如您所期望的:


员工ID11
员工姓名 电话号码 电话区号
詹姆斯 5 613
詹姆斯 6 416
但是当您在
FETCH

(

JOIN
) 上添加
FETCH JOIN
子句时,会发生以下情况:

员工ID1
员工姓名 电话号码 电话区号
詹姆斯 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'

也许在最新版本的 Hibernate 中,您需要使用
SELECT DISTINCT
以避免重复结果。


我可能会晚回答这个问题,但从我的角度来看。

3
投票
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'
)

我们仅获取在某些地区有电话号码的员工数据
这应该有助于在每次查询后获得想法。

© www.soinside.com 2019 - 2024. All rights reserved.