如何正确表达JPQL"join fetch"和"where"子句为JPA 2 CriteriaQuery?

chr*_*ris 47 java jpa criteria jpa-2.0

考虑以下JPQL查询:

SELECT foo FROM Foo foo
INNER JOIN FETCH foo.bar bar
WHERE bar.baz = :baz
Run Code Online (Sandbox Code Playgroud)

我正在尝试将其转换为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);
Run Code Online (Sandbox Code Playgroud)

这里显而易见的问题是我正在进行两次相同的连接,因为Fetch<Foo, Bar>似乎没有方法来获取Path.有没有办法避免必须加入两次?或者我是否必须坚持使用简单的查询这么好的旧JPQL?

Jam*_*mes 67

在JPQL中,规范中的情况也是如此.JPA规范不允许将别名赋予获取连接.问题在于,通过限制连接提取的上下文,您可以轻松地用脚拍摄自己.加入两次会更安全.

这通常是ToMany比ToOnes更多的问题.例如,

Select e from Employee e 
join fetch e.phones p 
where p.areaCode = '613'
Run Code Online (Sandbox Code Playgroud)

这将错误地返回包含"613"区号中的数字的所有员工,但会遗漏返回列表中其他区域的电话号码.这意味着拥有613和416区号的电话的员工将丢失416电话号码,因此该对象将被破坏.

当然,如果你知道自己在做什么,那么额外的连接是不可取的,一些JPA提供者可能允许对连接提取进行别名,并且可能允许将Criteria Fetch转换为连接.

  • 另请参见http://java-persistence-performance.blogspot.com/2012/04/objects-vs-data-and-filtering-join.html (4认同)
  • 非常好的回答,谢谢.没有想到这一点.Hibernate从来没有向我抱怨过fetch连接的别名,我不知道这实际上违反了规范. (3认同)

Dhe*_*rik 10

我将使用James回答的一个很好的例子并添加替代解决方案来直观地展示问题.

当您执行以下查询时,没有FETCH:

Select e from Employee e 
join e.phones p 
where p.areaCode = '613'
Run Code Online (Sandbox Code Playgroud)

您将按照Employee预期获得以下结果:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613
1          | James        | 6       | 416
Run Code Online (Sandbox Code Playgroud)

但是当你添加FETCH单词时JOIN,会发生以下情况:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613
Run Code Online (Sandbox Code Playgroud)

生成的SQL是这两个查询相同,但在Hibernate中删除内存416,当你使用寄存器WHEREFETCH加入.

因此,要带上所有手机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'
Run Code Online (Sandbox Code Playgroud)

  • 你能用SQL查询更多地解释一下吗,为什么第一个查询会返回areaCode 613和416的员工因为有一个where子句,如果我理解正确的话那么Employee和Phone之间会有一个内部连接。那为什么会返回613和416呢?它不应该只返回 613 吗? (3认同)
  • @FaizanAhmad 如果我们用纯 SQL 思考,你是对的,但在这里我们选择可以“嵌入”关系的实体。“Employee”实体包含员工的所有电话的列表(通常是“OneToMany”关联)。因此,当我们选择整个员工实体(“e”)时,无论“where”子句是什么,它确实应该包含该员工的所有电话。这里的问题是,当使用“fetch”时,hibernate 在内部从“e.phones”列表中排除“where”子句中或不在“where”子句中的值。 (2认同)