如何正确地使用JPA 2 CriteriaQuery来表达JPQL中的"join fetch"和"where"子句?

16 浏览
0 Comments

如何正确地使用JPA 2 CriteriaQuery来表达JPQL中的"join fetch"和"where"子句?

考虑以下的JPQL查询:

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

我正在尝试将其翻译成Criteria查询。目前为止,我已经做到了这一点:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Foo.class);
Root r = cq.from(Foo.class);
Fetch fetch = r.fetch(Foo_.bar, JoinType.INNER);
Join join = r.join(Foo_.bar, JoinType.INNER);
cq.where(cb.equal(join.get(Bar_.baz), value);

明显的问题是我做了两次相同的连接,因为Fetch似乎没有获取Path的方法。

有没有办法避免连接两次?还是我必须坚持使用简单的JPQL查询?

0
0 Comments

如何正确地使用JPA 2 CriteriaQuery表达JPQL中的“join fetch”与“where”子句?

问题的出现原因是在使用JPA 2 CriteriaQuery时,需要正确地将JPQL中的“join fetch”与“where”子句转换为相应的SQL查询语句。

解决方法是根据不同的JPQL查询,将其转换为相应的SQL查询语句。

第一个JPQL查询是:

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

第二个JPQL查询是:

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'  

第三个JPQL查询是:

Select e from Employee e

join e.phones p

where p.areaCode = '613'

对应的SQL查询语句是:

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

以上就是解决该问题的方法,通过将JPQL查询转换为相应的SQL查询语句,可以正确地使用JPA 2 CriteriaQuery表达JPQL中的“join fetch”与“where”子句。

0
0 Comments

问题的出现原因是使用JPQL的"join fetch"和"where"子句时,Hibernate会在内存中过滤掉不符合"where"条件的结果,导致查询结果不符合预期。

解决方法是使用两个"JOIN"子句,一个用于"where"条件,另一个用于"fetch"操作。

具体地,我们可以这样写JPQL查询语句:

Select e from Employee e

join e.phones p

join fetch e.phones

where p.areaCode = '613'

这样可以保证查询结果中包含所有的phones,并且正确应用"where"条件。

对于最新版本的Hibernate,可能需要使用"SELECT DISTINCT"来避免重复的结果。

关于为什么第一个查询会返回areaCode为613和416的employee,可以这样解释:第一个查询只返回了employee实体"James",因为他的两个phones中有一个的areaCode为613。当使用"fetch" join时,一些开发人员希望也能收到这两个phones,因为其中一个的areaCode为613,但是Hibernate会在内存中过滤掉areaCode不为613的结果,只返回areaCode为613的结果。因此,我们需要使用两个join子句,一个用于"fetch"操作(获取所有的phones),另一个用于"where"条件(返回任何一个phone的areaCode为613的employee)。

关于性能方面的问题,使用这种解决方法会产生n^2行数据,其中n是集合的大小。在纯SQL的角度来看,你是对的。但是这里我们选择的是查询实体,实体可以"嵌入"关系。Employee实体包含了一个employee的所有phones的列表(通常是OneToMany关联)。所以,当我们选择整个employee实体时(e),无论where子句是什么,它都应该包含该employee的所有phones。问题在于当使用fetch时,Hibernate在内部从e.phones列表中排除了不符合where子句的值。

对于最新版本,使用这种解决方法会产生重复的结果行。可以通过使用"SELECT DISTINCT"来修复这个问题。

0
0 Comments

问题的出现原因是在JPQL中,JPA规范不允许给fetch join指定一个别名。这个问题在一对多的关系中更为常见。例如,下面的查询语句会返回所有在'613'区号下的电话号码对应的员工,但是会忽略其他区号的电话号码,这会导致员工对象被破坏。如果需要避免这个问题,可以使用两次join。

解决方法是使用两次join,或者有些JPA提供者允许给join fetch指定别名,也可以将Criteria Fetch转换为Join。

参考文章:http://java-persistence-performance.blogspot.com/2012/04/objects-vs-data-and-filtering-join.html

有一位用户表示,他遇到了同样的问题,并且这个解决方法解决了他的困惑。还有一位用户认为这个回答是错误的,他的查询语句只返回了所有电话号码都是'613'区号的员工,而没有过滤掉其他区号的电话号码。还有一位用户表示,他在使用这个解决方法时遇到了问题,当添加了多个join fetch时,相同的电话号码会被多次添加。

0