请帮助我了解在哪里使用常规JOIN和在哪里使用JOIN FETCH。例如,如果我们有这两个查询

FROM Employee emp
JOIN emp.department dep

and

FROM Employee emp
JOIN FETCH emp.department dep

它们之间有什么区别吗?如果是,什么时候用哪一个?


当前回答

在这两个查询中,您使用JOIN查询至少有一个部门关联的所有员工。

但是,不同之处在于:在第一个查询中,您只返回Hibernate的employees。在第二个查询中,您将返回employees和所有相关的department。

因此,如果使用第二个查询,您将不需要再次执行新的查询来访问数据库以查看每个Employee的部门。

当您确定需要每个Employee的Department时,可以使用第二个查询。如果不需要Department,请使用第一个查询。

我建议阅读这个链接,如果你需要应用一些WHERE条件(你可能会需要):如何正确表达JPQL“join fetch”与“WHERE”子句作为JPA 2 CriteriaQuery?

更新

如果您不使用fetch而继续返回Departments,是因为您的Employee和Department(一个@OneToMany)之间的映射是使用FetchType.EAGER设置的。在这种情况下,使用FROM Employee进行的任何HQL(带fetch或不带fetch)查询都将带来所有department。记住,所有的映射*ToOne (@ManyToOne和@OneToOne)默认情况下都是EAGER。

其他回答

Dherik:我不确定你说的是什么,当你不使用fetch时,结果将是类型:List<Object[]>,这意味着对象表的列表,而不是雇员的列表。

Object[0] refers an Employee entity 
Object[1] refers a Departement entity 

当您使用fetch时,只有一个选择,结果是Employee list <Employee>的列表,其中包含部门列表。它覆盖了实体的惰性声明。

如果你有@oneToOne映射设置为FetchType。LAZY,你使用第二个查询(因为你需要Department对象作为Employee对象的一部分被加载)Hibernate将做的是,它将为从DB中获取的每个Employee对象发出查询来获取Department对象。

稍后,在代码中,您可以通过Employee到Department单值关联访问Department对象,Hibernate将不会发出任何查询来获取给定Employee的Department对象。

请记住,Hibernate仍然发出与它所获取的employee数量相等的查询。如果您希望访问所有Employee对象的Department对象,Hibernate将在上述两个查询中发出相同数量的查询

差异连接和连接FETCH在JPQL

TLDR: FETCH关键字告诉entityManager也获取急切连接的关联实体(当还没有这种情况时)。


假设我们有一个user实体和userInfo实体。用户实体与userInfo有@OneToMany关系,如下所示:

import javax.persistence.*;

@Entity
@Table(name= "test_user")
public class User {

    // ... user properties id etc

    @OneToMany(mappedBy = "user" fetch = FetchType.LAZY)
    private List<UserInfo> infoList;
}

假设我们有以下查询(这是Spring数据JPA语法,但与JPQL如何构造无关):

@Query("SELECT user FROM User user JOIN user.infoList info")
public List<User> getUsersJoin();

@Query("SELECT user FROM User user JOIN FETCH user.infoList info")
public List<User> getUsersJoinFetch();

第一个只有JOIN关键字的查询将生成以下SQL:

select u.id, u.email, u.name from test_user u
inner join test_user_data on u.id=test_user_data.user_id;

正如您所看到的,它只从test_user表中获取数据,而不是从test_user_data表中获取数据。在调试器中也可以看到,如下所示:

可以看到,我们的User对象上没有List<userData>,因为默认情况下它没有加载。


现在让我们用JOIN FETCH检查查询生成的SQL:

select test_user.id, data.id, test_user.email, test_user.name, 
data.data, data.user_id, data.user_id, data.id from test_user test_user 
inner join test_user_data data on test_user.id=data.user_id

可以看到,我们现在从连接中的test_user和test_user_data表中获取数据。在调试器中也可以看到,如下所示:

可以看到,我们现在可以访问User对象中的List<userData>。

在这两个查询中,您使用JOIN查询至少有一个部门关联的所有员工。

但是,不同之处在于:在第一个查询中,您只返回Hibernate的employees。在第二个查询中,您将返回employees和所有相关的department。

因此,如果使用第二个查询,您将不需要再次执行新的查询来访问数据库以查看每个Employee的部门。

当您确定需要每个Employee的Department时,可以使用第二个查询。如果不需要Department,请使用第一个查询。

我建议阅读这个链接,如果你需要应用一些WHERE条件(你可能会需要):如何正确表达JPQL“join fetch”与“WHERE”子句作为JPA 2 CriteriaQuery?

更新

如果您不使用fetch而继续返回Departments,是因为您的Employee和Department(一个@OneToMany)之间的映射是使用FetchType.EAGER设置的。在这种情况下,使用FROM Employee进行的任何HQL(带fetch或不带fetch)查询都将带来所有department。记住,所有的映射*ToOne (@ManyToOne和@OneToOne)默认情况下都是EAGER。

JOIN

当对实体关联使用JOIN时,JPA将在生成的SQL语句中的父实体和子实体表之间生成一个JOIN。

举个例子,当执行这个JPQL查询时:

FROM Employee emp
JOIN emp.department dep

Hibernate将生成以下SQL语句:

SELECT emp.*
FROM employee emp
JOIN department dep ON emp.department_id = dep.id

注意,SQL SELECT子句只包含员工表列,而不包含部门表列。要获取部门表列,我们需要使用JOIN fetch而不是JOIN。

加入取回

因此,与JOIN相比,JOIN FETCH允许您在生成的SQL语句的SELECT子句中投影连接表列。

所以,在你的例子中,当执行这个JPQL查询时:

FROM Employee emp
JOIN FETCH emp.department dep

Hibernate将生成以下SQL语句:

SELECT emp.*, dept.*
FROM employee emp
JOIN department dep ON emp.department_id = dep.id

请注意,这一次也选择了部门表列,而不仅仅是与FROM JPQL子句中列出的实体相关联的列。

另外,当使用Hibernate时,JOIN FETCH是解决LazyInitializationException的好方法,因为您可以使用FetchType初始化实体关联。LAZY抓取策略和你要抓取的主实体。