考虑以下 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 并进行如此简单的查询?
在 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 强制转换为联接。
当您执行以下查询时,没有
FETCH
:
Select e from Employee e
join e.phones p
where p.areaCode = '613'
您将从
Employee
获得以下结果,正如您所期望的:
员工姓名 | 电话号码 | 电话区号 | |
---|---|---|---|
詹姆斯 | 5 | 613 | |
詹姆斯 | 6 | 416 |
FETCH
(
JOIN
) 上添加 FETCH JOIN
子句时,会发生以下情况:
员工姓名 | 电话号码 | 电话区号 | |
---|---|---|---|
詹姆斯 | 5 | 613 |
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
以避免重复结果。我可能会晚回答这个问题,但从我的角度来看。
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'
)
我们仅获取在某些地区有电话号码的员工数据这应该有助于在每次查询后获得想法。