[英]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.