当使用JPA和Hibernate时,JOIN和JOIN FETCH有什么区别?

304

请帮我理解何时应该使用普通JOIN,何时应该使用JOIN FETCH。例如,如果我们有以下两个查询:

FROM Employee emp
JOIN emp.department dep

FROM Employee emp
JOIN FETCH emp.department dep

它们之间有区别吗?如果有,该在什么情况下使用哪一个?


4
你可以在这里找到链接(http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html),阅读第14.3节“关联和连接”。 - Angga
9
我已阅读该文档,但仍不知道应何时使用JOIN和何时使用JOIN FETCH。 - abbas
4
如果您将@oneToOne映射设置为FetchType.LAZY,并且使用第二个查询(因为您需要将Department对象作为Employee对象的一部分加载),那么Hibernate将会针对从数据库获取的每个单独的Employee对象发出查询以获取Department对象。稍后在代码中,您可以通过Employee访问Department对象来获取单个值关联,并且Hibernate不会发出任何查询以获取给定Employee的Department对象。请记住,Hibernate仍然会发出与它所检索到的员工数量相等的查询。 - Bunti
为协助文档查找~ 获取策略 - Edward J Beckett
如何处理部门表中没有数据的情况。 - Shameera Anuranga
1
@ShameeraAnuranga 我认为在这种情况下,你需要使用左外连接。 - abbas
6个回答

311
在这两个查询中,您使用JOIN查询所有至少有一个关联部门的员工。
但是,区别在于:在第一个查询中,您仅返回Hibernate所需的员工。在第二个查询中,您返回员工所有关联的部门。
因此,如果您使用第二个查询,您将不需要再次查询数据库以查看每个员工的部门。
当您确定需要每个员工的部门时,可以使用第二个查询。如果您不需要部门,请使用第一个查询。
如果您需要应用一些WHERE条件(您可能需要的条件),我建议阅读此链接:如何正确表达带有JPA 2 CriteriaQuery的“join fetch”和“where”子句的JPQL语句? 更新 如果您不使用fetch,并且部门仍然被返回,那是因为您在Employee和Department之间的映射(一个@OneToMany)被设置为FetchType.EAGER。在这种情况下,任何带有FROM Employee的HQL查询(无论是否带有fetch)都会带来所有的部门。请记住,所有的*ToOne映射(@ManyToOne@OneToOne)默认都是EAGER加载的。

1
如果我们在不获取结果的情况下执行语句,会出现什么行为。然后在会话中我们将如何处理部门? - gstackoverflow
1
@gstackoverflow,是的。 - Dherik
我在关系的两端使用本地查询和延迟加载,但仍然会加载子关系的层次结构。 - Davoud Badamchi
@Dherik 有没有办法使用nativeQuery=true来使用这个查询? - Robs
@Robs 如果您使用 nativeQuery,则不再使用 JPQL/HQL 语言来创建查询;而是使用纯 SQL。因此,fetch 不可用。 - Dherik
显示剩余3条评论

90

JOIN(连接)

使用JOIN(连接)查询实体的关联关系时,JPA会在生成的SQL语句中生成父实体和子实体表之间的连接。

因此,在执行此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子句仅包含employee表列,而不是department表列。要获取department表列,我们需要使用JOIN FETCH而不是JOIN。

JOIN FETCH

因此,与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
请注意,这一次选择了department表的列,而不仅仅是与FROM JPQL语句中的实体相关联的列。

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


3
在同一查询中使用多个JOIN FETCH是否可行? - A.Onur Özcan
2
是的,任意数量的@ManyToOne@OneToOne关联都是可能的,最多只能有一个集合。 - Vlad Mihalcea

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

通过“fetch”连接,可以使用单个select初始化关联或值的集合及其父对象。 这在集合的情况下特别有用。 它有效地覆盖了映射文件中的外连接和惰性声明

如果您在实体内有(fetch = FetchType.LAZY)属性的集合,则此“JOIN FETCH”将产生效果(例如下面的示例)。

它仅影响“查询何时发生”的方法。 您还必须了解此内容

Hibernate具有两个正交概念:关联的获取时间和获取方式。 不要混淆它们很重要。 我们使用获取来调整性能。 我们可以使用惰性定义某个类的任何分离实例中始终可用的数据的契约。

关联获取时间-->您的“FETCH”类型

如何获取-->Join/select/Subselect/Batch

在您的情况下,FETCH仅在Employee实体内有department作为集合时才会生效,例如下面的示例:

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

当你使用时

FROM Employee emp
JOIN FETCH emp.department dep

当你没有使用fetch时,你会获得empemp.dep。但是,如果你需要emp.dep,Hibernate将再次向数据库发出查询以获取该部门的集合。

因此,这只是性能调整的问题,关于你想在单个查询中获取所有结果(无论你是否需要),还是在需要时查询它们(延迟加载)。

当你需要在一个查询中获取少量数据时(一次大查询),请使用急切加载。或者使用延迟加载,在需要时进行查询(许多较小的查询)。

使用fetch的情况:

  • 实体中不存在大的不必要的集合/ set

  • 应用服务器与数据库服务器之间的通信距离太远,需要很长时间

  • 当你没有访问权限时,可能需要该集合/ set(事务性方法 / 类之外)


你能解释一下我在更新的问题中刚刚写的查询吗? - abbas
有用的考虑:在你要获取的实体内部不要有大型不必要的集合/集合。 - Divs
如果员工内部的部门是一个List而不是一个Set,那么这些部门是否仍然会被热切地获取? - Stephane
在JPQL语句中使用FETCH关键字是否意味着立即检索属性? - Stephane

6

JPQL中JOIN和JOIN FETCH的区别

简介:FETCH关键字告诉entityManager也获取与之前已经关联的实体(在此之前没有关联的情况下)。


假设我们有一个用户实体和用户信息实体。用户实体有一个如下@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 Data 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表获取数据。这也可以在调试器中看到,如下所示: enter image description here 正如所见,我们的User对象上没有List,因为默认情况下不会加载它。
现在让我们检查使用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表中进行了连接并获取了数据。这也可以在调试器中查看,如下图:

enter image description here

正如所观察到的那样,我们现在可以访问User对象内的List<userData>。


2
从这个看来,join 是毫无意义的,这不可能是真的...对吧?我错过了什么? - marcotama
你仍然可以在连接的表上设置条件,例如 ... where test_user_data.some_field = some_value。 这样一来,你就能获取满足条件的用户,但不会加载后续不需要的数据。 - aminator

6
如果您将@oneToOne映射设置为FetchType.LAZY,并且您使用第二个查询(因为您需要将Department对象作为Employee对象的一部分加载),那么Hibernate将执行查询以获取每个单独的Employee对象的Department对象。稍后,在代码中,您可能通过Employee访问Department对象以获取Department到单值关联,并且Hibernate不会发出任何查询以获取给定Employee的Department对象。 请记住,Hibernate仍会发出与其已获取的Employee数量相同的查询。如果您希望访问所有Employee对象的Department对象,则上述两个查询都将发出相同数量的查询。

2

Dherik: 我不确定你所说的内容,如果你不使用fetch,结果将会是List<Object[ ]>类型,这意味着一个对象表的列表而不是一个员工列表。

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

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

我不确定我是否理解您的关注点。如果您不使用 fetch,则查询将仅返回员工。即使在这种情况下,如果部门仍然被返回,则是因为您的 Employee 和 Department 之间的映射(@OneToMany)设置为 FetchType.EAGER。在这种情况下,任何带有 FROM Employee 的 HQL 查询(无论是否使用 fetch)都会带来所有部门。 - Dherik
如果不使用fetch(仅使用join),则结果将是一个集合数组,其中有两行,第一行是员工的集合,第二行是部门的集合。使用急切或延迟fetch,将获取部门信息。 - Bilal BBB
如果在HQL中没有使用fetch,那么只有当Employee和Department之间的映射是EAGER(@OneToMany(fetch = FetchType.EAGER))时才会发生这种情况。如果不是这种情况,部门将不会被返回。 - Dherik
@Dherik 你自己试试,你会得到一个ClassCastException异常。 - Bilal BBB
我找到了问题所在。不是获取(fetch)的问题,而是HQL中select语句的编写方式。请尝试使用SELECT emp FROM Employee emp JOIN FETCH emp.department dep。当你省略SELECT部分时,JPA/Hibernate会返回一个Object[]列表。 - Dherik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接