我有两个表Employee和Department以下是这两个表的实体类
Department.java
@Entity
@Table(name = "DEPARTMENT")
public class Department {
@Id
@Column(name = "DEPARTMENT_ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer departmentId;
@Column(name = "DEPARTMENT_NAME")
private String departmentName;
@Column(name = "LOCATION")
private String location;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
@Fetch(FetchMode.SUBSELECT)
//@Fetch(FetchMode.JOIN)
private List<Employee> employees = new ArrayList<>();
}
Employee.java
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
@Id
@SequenceGenerator(name = "emp_seq", sequenceName = "seq_employee")
@GeneratedValue(generator = "emp_seq")
@Column(name = "EMPLOYEE_ID")
private Integer employeeId;
@Column(name = "EMPLOYEE_NAME")
private String employeeName;
@ManyToOne
@JoinColumn(name = "DEPARTMENT_ID")
private Department department;
}
以下是我em.find(Department.class, 1);
- 获取模式= fetchmode.join
SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_,
department0_.DEPARTMENT_NAME AS DEPARTMENT_NAME2_0_0_,
department0_.LOCATION AS LOCATION3_0_0_,
employees1_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_1_,
employees1_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_1_,
employees1_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_2_,
employees1_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_2_,
employees1_.EMPLOYEE_NAME AS EMPLOYEE_NAME2_1_2_
FROM DEPARTMENT department0_
LEFT OUTER JOIN EMPLOYEE employees1_
ON department0_.DEPARTMENT_ID =employees1_.DEPARTMENT_ID
WHERE department0_.DEPARTMENT_ID=?
- 获取模式= fetchmode.subselect
SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_,
department0_.DEPARTMENT_NAME AS DEPARTMENT_NAME2_0_0_,
department0_.LOCATION AS LOCATION3_0_0_
FROM DEPARTMENT department0_
WHERE department0_.DEPARTMENT_ID=?
SELECT employees0_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_0_,
employees0_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_0_,
employees0_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_1_,
employees0_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_1_,
employees0_.EMPLOYEE_NAME AS EMPLOYEE_NAME2_1_1_
FROM EMPLOYEE employees0_
WHERE employees0_.DEPARTMENT_ID=?
我只是想知道我们应该选择哪一个FetchMode.JOIN
或FetchMode.SUBSELECT
?我们应该选择哪种情况?
答案 0 :(得分:27)
Marmite引用的SUBQUERY策略与FetchMode.SELECT有关,而与SUBSELECT无关。
您发布的关于 fetchmode.subselect 的控制台输出很奇怪,因为这不是应该工作的方式。
使用子选择查询加载其他集合
休眠docs:
如果必须提取一个惰性集合或单值代理,Hibernate将加载所有这些,在子选择中重新运行原始查询。这与批量获取的工作方式相同,但没有零碎的加载。
FetchMode.SUBSELECT应如下所示:
SELECT <employees columns>
FROM EMPLOYEE employees0_
WHERE employees0_.DEPARTMENT_ID IN
(SELECT department0_.DEPARTMENT_ID FROM DEPARTMENT department0_)
您可以看到第二个查询将内存所有属于某个部门的员工(即employee.department_id不为空),如果不是部门则无关紧要您在第一个查询中检索的内容。 因此,如果员工表很大,这可能是一个主要问题,因为它可能是accidentially loading a whole database into memory。
但是,FetchMode.SUBSELECT显着减少了查询的数量,因为与FecthMode.SELECT的N + 1个查询相比只需要两个查询。
您可能认为FetchMode.JOIN的查询次数更少,只有1次,那么为什么要使用SUBSELECT呢?嗯,这是真的,但代价是重复数据和更重的响应。
如果必须使用JOIN获取单值代理,则查询可以检索:
+---------------+---------+-----------+
| DEPARTMENT_ID | BOSS_ID | BOSS_NAME |
+---------------+---------+-----------+
| 1 | 1 | GABRIEL |
| 2 | 1 | GABRIEL |
| 3 | 2 | ALEJANDRO |
+---------------+---------+-----------+
如果老板的员工数据指向多个部门并且带有成本,则该副本的员工数据会重复。
如果必须使用JOIN获取延迟集合,则查询可能会检索:
+---------------+---------------+-------------+
| DEPARTMENT_ID | DEPARTMENT_ID | EMPLOYEE_ID |
+---------------+---------------+-------------+
| 1 | Sales | GABRIEL |
| 1 | Sales | ALEJANDRO |
| 2 | RRHH | DANILO |
+---------------+---------------+-------------+
如果部门数据包含多个员工(自然案例),则该部门数据会重复。 我们不仅要承担带宽成本,还要重复duplicated Department objects,我们必须使用SET或DISTINCT_ROOT_ENTITY去重复。
然而,在许多情况下,像延迟较低的pos中的重复数据是很好的折衷,例如Markus Winand says。
SQL连接仍然比嵌套选择方法更有效 - 即使它执行相同的索引查找 - 因为它避免了大量的网络通信。如果由于每次销售的员工属性的重复而传输的数据总量更大,那么会更快。这是因为性能的两个方面:响应时间和吞吐量;在计算机网络中,我们称之为延迟和带宽。带宽对响应时间的影响很小,但延迟会产生巨大影响。这意味着数据库往返次数对响应时间的影响比传输的数据量更重要。
因此,使用SUBSELECT的主要问题是hard to control,可能正在将整个实体图加载到内存中。 使用批量提取,您可以在单独的查询中获取关联实体作为SUBSELECT(因此您不会遭受重复),逐渐地,最重要的是您只查询相关实体(因此您不会受到可能加载大图的影响),因为IN子查询由outter查询检索的ID过滤。
Hibernate:
select ...
from mkyong.stock stock0_
Hibernate:
select ...
from mkyong.stock_daily_record stockdaily0_
where
stockdaily0_.STOCK_ID in (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
(如果使用非常高的批量大小的批量提取将像SUBSELECT一样但没有加载整个表的问题,这可能是一个有趣的测试)
一些帖子显示了不同的提取策略和SQL日志(非常重要):
要点:
表格是使用ascii-tables构建的。
答案 1 :(得分:9)
我说这取决于......
假设您在一个部门中有N名员工,其中包含D字节信息,平均员工包含E字节。 (字节是属性长度与一些开销的总和)。
使用 join 策略执行1次查询并传输N *(D + E)数据。
使用子查询策略执行1 + N次查询,但只传输D + N * E数据。
如果N很大, N + 1查询通常是 NO GO ,因此首选JOIN。
但实际上,您必须检查查询次数和数据传输之间的里程数。
请注意,我并未将其他方面视为Hibernate缓存。
如果员工表很大并且已分区,则其他细微方面可能有效 - 对索引访问的分区修剪也会考虑。
答案 2 :(得分:1)
我的客户(金融服务)也有类似的问题,他想“在一个查询中获取数据”。好吧,我解释说最好有多个查询,因为以下内容:
对于FetchMode.JOIN,每个员工将部门从数据库转移到应用程序一次,因为连接操作会导致每个员工的部门倍增。如果你有10个部门,每个部门有100名员工,那么这10个部门中的每一个都将在一个查询中传输100次,简单的SQL。因此,在这种情况下,每个部门的转移次数超过了必要的99倍,从而导致部门的数据传输费用。
对于Fetchmode SUBSELECT,将向数据库触发两个查询。一个用于获取1000名雇员的数据,一个用于获得10个部门。对我来说,这听起来效率更高。您肯定会确保索引到位,以便可以立即检索数据。
我更喜欢FetchMode.SUBSELECT。
如果每个部门只有一名员工,那将是另一种情况,但正如“部门”所暗示的那样,这种情况不太可能发生。
我建议测量访问时间以支持这一理论。对于我的客户,我对不同类型的访问进行了测量,而我的客户的“部门”表有更多的字段(尽管我没有设计它)。所以很快就可以看出FetchMode.SUBSELECT要快得多。
答案 3 :(得分:1)
普兰基说
(1)这极具误导性。 (2)子选择不会将整个数据库提取到内存中。链接的文章是关于一个怪癖,其中subselect(3)忽略来自父级的分页命令,(4)但它仍然是一个子选择。
您发布的有关fetchmode.subselect的控制台输出很奇怪,因为这不是应该工作的方式。
这是事实,但只有在部门实体被隐藏的情况下(这意味着多个员工集合未初始化),我已使用3.6.10.Final和4.3.8.Final对其进行了测试 在方案2.2 (FetchMode.SUBSELECT hidrating 2 of 3 Departments)和3.2 (FetchMode.SUBSELECT hidrating all Departments)中,SubselectFetch.toSubselectString返回以下内容(Hibernate类的链接取自4.3.8.Final标记):
select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_
此子查询用于构建OneToManyJoinWalker.initStatementString以
结尾的where子句employees0_.DEPARTMENT_ID in (select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_)
然后在以{/ 3>结尾的CollectionJoinWalker.whereString中添加where子句
select employees0_.DEPARTMENT_ID as DEPARTMENT3_2_1_, employees0_.EMPLOYEE_ID as EMPLOYEE1_1_, employees0_.EMPLOYEE_ID as EMPLOYEE1_3_0_, employees0_.DEPARTMENT_ID as DEPARTMENT3_3_0_, employees0_.EMPLOYEE_NAME as EMPLOYEE2_3_0_ from SUBSELECT_EMPLOYEE employees0_ where employees0_.DEPARTMENT_ID in (select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_)
确认此查询,在这两种情况下,所有员工都被检索并补充水分。 这显然是方案2.2中的一个问题,因为我们只为第1和第2部门提供保湿服务,但即使他们不属于这些部门(在这种情况下属于部门3的员工),也为所有员工提供保湿。
如果会话中只有一个部门实体与其员工集合未初始化,那么查询就像eatSleepCode编写的那样。查看scenario 1.2
select subselectd0_.department_id as departme1_2_0_, subselectd0_.department_name as departme2_2_0_, subselectd0_.location as location3_2_0_ from subselect_department subselectd0_ where subselectd0_.department_id=?
/**
* Performs a separate SQL select to load the indicated data. This can either be eager (the second select is
* issued immediately) or lazy (the second select is delayed until the data is needed).
*/
SELECT,
/**
* Inherently an eager style of fetching. The data to be fetched is obtained as part of an SQL join.
*/
JOIN,
/**
* Initializes a number of indicated data items (entities or collections) in a series of grouped sql selects
* using an in-style sql restriction to define the batch size. Again, can be either eager or lazy.
*/
BATCH,
/**
* Performs fetching of associated data (currently limited to only collections) based on the sql restriction
* used to load the owner. Again, can be either eager or lazy.
*/
SUBSELECT
到现在为止,我无法解决这个Javadoc的含义:
<击>
基于用于加载所有者的sql限制
击>
<强>更新强> 普莱斯基说:
相反,它只会在最坏的情况下加载表格,即便如此,只有当您的初始查询没有where子句时才会加载。因此,我会说使用子选择查询可能会意外地加载整个表格,如果您限制结果并且您没有任何WHERE标准。
这是真的,这是我在新scenario 4.2
中测试的非常重要的细节为获取员工而生成的查询是
select employees0_.department_id as departme3_4_1_, employees0_.employee_id as employee1_5_1_, employees0_.employee_id as employee1_5_0_, employees0_.department_id as departme3_5_0_, employees0_.employee_name as employee2_5_0_ from subselect_employee employees0_ where employees0_.department_id in (select this_.department_id from subselect_department this_ where this_.department_name>=?)
where子句中的子查询包含原始限制 this_.department_name&gt; =?,从而避免了所有Employees的加载。 这就是javadoc的含义
基于用于加载所有者的SQL限制
我所说的关于FetchMode.JOIN的所有内容以及与FetchMode.SUBSELECT的差异仍然存在(并且也适用于FetchMode.SELECT)。