请帮助我了解在哪里使用常规JOIN和在哪里使用JOIN FETCH。例如,如果我们有这两个查询
FROM Employee emp
JOIN emp.department dep
and
FROM Employee emp
JOIN FETCH emp.department dep
它们之间有什么区别吗?如果是,什么时候用哪一个?
请帮助我了解在哪里使用常规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>。