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

2022-08-31 15:09:32

请考虑以下 JPQL 查询:

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

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

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

这里明显的问题是,我正在执行两次相同的连接,因为似乎没有方法可以获得.有没有办法避免必须加入两次?还是我必须坚持使用像这样简单的查询的旧的JPQL?Fetch<Foo, Bar>Path


答案 1

在JPQL中,规范中实际上也是如此。JPA 规范不允许为获取连接指定别名。问题在于,通过限制联接提取的上下文,您可以轻松地用它来射击自己。加入两次更安全。

这通常更像是ToMany的问题,而不是ToOnes的问题。例如

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

这将错误地返回所有包含“613”区号中数字的员工,但会在返回的列表中省略其他区域的电话号码。这意味着在 613 和 416 区号中拥有电话的员工将丢失 416 电话号码,因此该对象将损坏。

当然,如果您知道自己在做什么,则不需要额外的连接,某些 JPA 提供程序可能允许为连接提取指定别名,并且可能允许将条件提取强制转换为连接。


答案 2

我将直观地展示问题,使用詹姆斯答案中的好例子并添加替代解决方案。

当您执行以下查询时,不带 :FETCH

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

您将获得以下结果,如您所料:Employee

员工身份 员工姓名 手机号码 电话区域代码
1 詹姆斯 5 613
1 詹姆斯 6 416

但是,当您在 () 上添加子句时,将发生以下情况:FETCHJOINFETCH JOIN

员工身份 员工姓名 手机号码 电话区域代码
1 詹姆斯 5 613

生成的 SQL 对于这两个查询是相同的,但是当您在联接上使用时,Hibernate 会在内存中删除寄存器。416WHEREFETCH

因此,要携带所有电话正确应用,您需要有两个s:一个用于,另一个用于.喜欢:WHEREJOINWHEREFETCH

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

推荐