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

FROM Employee emp
JOIN emp.department dep

and

FROM Employee emp
JOIN FETCH emp.department dep

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


在我之前在评论中提到的这个链接中,阅读这部分:

“获取”连接允许值的关联或集合 使用单个选择连同它们的父对象一起初始化。 这在集合的情况下特别有用。它 类的外部连接和惰性声明 关联和集合的映射文件。

如果你有(FETCH = FetchType.LAZY)属性用于实体内的集合(示例如下),这个“JOIN FETCH”将会起作用。

它只对“查询应该何时发生”的方法起作用。你还必须知道:

Hibernate有两个正交的概念:何时获取关联和如何获取关联 这是牵强附会吗?重要的是不要把它们弄混。我们使用 获取来调优性能。我们可以使用lazy来定义契约 什么数据总是在一个特定的分离实例中可用 类。

什么时候取回关联——>你的“FETCH”类型

Join/select/Subselect/Batch

在你的例子中,FETCH只会在你有department作为Employee内部的集合时起作用,在实体中就像这样:

@OneToMany(fetch = FetchType.LAZY)
private Set<Department> department;

当你使用

FROM Employee emp
JOIN FETCH emp.department dep

你会得到emp和emp。dep。当你不使用fetch时,你仍然可以得到emp.dep,但hibernate将处理另一个select到数据库来获得那组department。

所以这只是一个性能调优的问题,关于你想要在一个查询中获得所有的结果(你需要它或不需要它)(即时抓取),或者你想在以后需要它时查询它(惰性抓取)。

当您需要通过一个选择(一个大查询)获取小数据时,使用即时抓取。或者使用惰性抓取来查询后面需要的东西(许多更小的查询)。

在以下情况使用fetch:

在您将要获取的实体中没有大的不需要的集合/集 从应用服务器到数据库服务器的通信距离太远,需要很长时间 稍后,当您无法访问该集合时(在事务方法/类之外),您可能需要它。


在这两个查询中,您使用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将在上述两个查询中发出相同数量的查询


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抓取策略和你要抓取的主实体。


差异连接和连接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>。