web-dev-qa-db-ja.com

JPA 2 CriteriaQueryとしてJPQLの「結合フェッチ」と「where」節を適切に表現する方法は?

次の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);

ここでの明らかな問題は、同じ結合を2回実行していることです。なぜならFetch<Foo, Bar>にはPathを取得するメソッドがないようです。 2回参加することを避ける方法はありますか?それとも、それと同じくらい簡単なクエリで古き良きJPQLに固執する必要がありますか?

47
chris

JPQLでは、仕様でも同じことが実際に当てはまります。 JPA仕様では、フェッチ結合にエイリアスを与えることは許可されていません。問題は、結合フェッチのコンテキストを制限することで、これで簡単に自分自身を撃つことができることです。 2回参加する方が安全です。

これは通常、ToOneよりもToManyの問題です。例えば、

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

これは、誤って '613'市外局番に番号を含むすべての従業員を返しますが、返されたリストの他の地域の電話番号は除外します。これは、613と416の市外局番に電話を持っていた従業員が416の電話番号を失うため、オブジェクトが破損することを意味します。

確かに、あなたが何をしているのか知っているなら、余分な結合は望ましくありません。一部のJPAプロバイダーは結合フェッチのエイリアスを許可し、Criteria Fetchを結合にキャストできる場合があります。

66
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

ただし、FETCHJOIN Wordを追加すると、次のようになります。

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613

生成されたSQLは2つのクエリで同じですが、Hibernateはメモリ上 the 416WHERE結合でFETCHを使用するときに登録します。

したがって、すべての電話を持ち込むには、およびWHEREを正しく適用するには、JOIN用とWHERE用の2つのFETCHが必要です。 [$ var] _。好む:

Select e from Employee e 
join e.phones p 
join fetch e.phones      //no alias, to not commit the mistake
where p.areaCode = '613'
7
Dherik