1. 概述
在使用对象关系映射(ORM)时,数据获取/加载通常分为两种类型:懒加载(lazy loading)和急加载(eager loading)。
本快速教程将讨论它们之间的差异,并演示如何在Hibernate中使用这些方法。
2. Maven 依赖
为了使用Hibernate,我们首先需要在pom.xml
中定义主要依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.2.Final</version>
</dependency>
Hibernate的最新版本可以在这里找到。
3. 懒加载与急加载
首先,让我们解释一下这两种加载策略的含义:
- 急加载(Eager Loading) 是一种设计模式,它会在对象初始化时立即加载数据。
- 懒加载(Lazy Loading) 是一种设计模式,我们使用它来推迟对象的初始化,直到确实需要时才进行。
接下来,我们将看看它是如何工作的。
首先,我们来看UserLazy
类:
@Entity
@Table(name = "USER")
public class UserLazy implements Serializable {
@Id
@GeneratedValue
@Column(name = "USER_ID")
private Long userId;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<OrderDetail> orderDetail = new HashSet();
// standard setters and getters
// also override equals and hashcode
}
然后是OrderDetail
类:
@Entity
@Table (name = "USER_ORDER")
public class OrderDetail implements Serializable {
@Id
@GeneratedValue
@Column(name="ORDER_ID")
private Long orderId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
private UserLazy user;
// standard setters and getters
// also override equals and hashcode
}
一个用户可以有多个订单详情。在急加载策略下,如果我们加载用户数据,它会同时加载所有关联的订单,并将它们存储在内存中。
然而,在启用懒加载时,如果我们获取一个UserLazy
实例,除非明确调用,否则OrderDetail
数据不会被初始化并加载到内存中。
在下一节,我们将看到如何在Hibernate中实现这个示例。
4. 加载配置
现在来看看如何在Hibernate中配置加载策略。
我们可以使用以下注解参数来启用懒加载:
fetch = FetchType.LAZY
对于急加载,我们使用这个参数:
fetch = FetchType.EAGER
为了设置急加载,我们使用了名为UserEager
的UserLazy
类的对应类。
在下一节,我们将比较这两种加载类型的差异。
5. 差异
如前所述,这两种加载方式的主要区别在于数据何时加载到内存中。
让我们来看一看:
List<UserLazy> users = sessionLazy.createQuery("From UserLazy").list();
UserLazy userLazyLoaded = users.get(3);
return (userLazyLoaded.getOrderDetail());
在懒加载策略下,orderDetailSet
只有在我们明确使用getter或其他方法调用时才会初始化:
UserLazy userLazyLoaded = users.get(3);
而在急加载的UserEager
中,它会在第一行立即初始化:
List<UserEager> user = sessionEager.createQuery("From UserEager").list();
对于懒加载,我们使用代理对象,并发出单独的SQL查询来加载orderDetailSet
。
在Hibernate中,禁用代理或懒加载被认为是不好的做法,因为它可能导致不必要的大量数据获取和存储。
我们可以使用以下方法测试功能:
Hibernate.isInitialized(orderDetailSet);
现在,让我们看看在两种情况下生成的查询:
<property name="show_sql">true</property>
fetching.hbm.xml
中的设置显示了生成的SQL查询。在控制台输出中,我们可以看到生成的查询。
对于懒加载,以下是在加载用户数据时生成的查询:
select user0_.USER_ID as USER_ID1_0_, ... from USER user0_
而在急加载中,我们看到了与USER_ORDER
的连接:
select orderdetai0_.USER_ID as USER_ID4_0_0_, orderdetai0_.ORDER_ID as ORDER_ID1_1_0_, orderdetai0_ ...
from USER_ORDER orderdetai0_ where orderdetai0_.USER_ID=?
这个查询针对所有用户,因此比其他方法消耗更多的内存。
6. 优缺点
6.1. 懒加载
优点:
- 初始加载时间比其他方法小得多
- 消耗的内存比其他方法少
缺点:
- 延迟初始化可能会影响性能,尤其是在不希望的时候。
- 在某些情况下,我们需要特别处理懒加载的对象,否则可能会导致异常。
6.2. 急加载
优点:
- 无延迟初始化相关的性能影响
缺点:
- 长的初始加载时间
- 加载太多不必要的数据可能会影响性能
7. JPA 默认FetchType
如前所见,fetch
属性可以是FetchType.LAZY
或FetchType.EAGER
。默认情况下,@OneToMany
和@ManyToMany
关联使用FetchType.LAZY
策略,而@OneToOne
和@ManyToOne
使用FetchType.EAGER
策略。
8. Hibernate 中的懒加载
Hibernate 通过提供实体和关联的代理实现,应用懒加载策略。它通过替换实体类的实现,拦截对实体的调用,以便在控制权转交给User
类实施之前,从数据库中加载缺失的信息。
还应注意,当关联表示为集合类(如上述示例中的Set<OrderDetail> orderDetailSet
)时,会创建一个包装器并替换原始集合。
要了解更多关于代理设计模式,请参考此处。
9. 结论
在这篇文章中,我们展示了Hibernate中使用的两种主要加载类型的例子。
对于更高级的知识,请查看Hibernate的官方网站。
要获取本文讨论的代码,请参阅此仓库。