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

wljmcqd8  于 2023-04-04  发布在  Java
关注(0)|答案(3)|浏览(100)

考虑以下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,使用这么简单的查询?

ntjbwcob

ntjbwcob1#

在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强制转换为一个Join。

epfja78i

epfja78i2#

我将使用James answer中的相同示例并添加替代解决方案来直观地展示问题。
当您执行以下查询时,不使用FETCH

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

您将从Employee获得以下结果,正如您所期望的:
| 员工ID|员工姓名|电话ID|电话区域代码|
| --------------|--------------|--------------|--------------|
| 1|詹姆斯|五|六一三|
| 1|詹姆斯|六|四一六|
但是,当您在JOINFETCH JOIN)上添加FETCH子句时,会发生以下情况:
| 员工ID|员工姓名|电话ID|电话区域代码|
| --------------|--------------|--------------|--------------|
| 1|詹姆斯|五|六一三|
这两个查询生成的SQL是相同的,但是当您在FETCH连接中使用WHERE时,Hibernate会删除内存中416寄存器。
因此,要使所有电话正确应用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来避免重复的结果。

ds97pgxw

ds97pgxw3#

我可能会晚些时候回答这个问题,但从我的Angular 来看。

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'
)

在那里我们只得到员工的数据,有一个电话号码,在某些地区
这应该有助于在每次查询后获得想法。

相关问题