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

为了设置急加载,我们使用了名为UserEagerUserLazy类的对应类。

在下一节,我们将比较这两种加载类型的差异。

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.LAZYFetchType.EAGER。默认情况下,@OneToMany@ManyToMany关联使用FetchType.LAZY策略,而@OneToOne@ManyToOne使用FetchType.EAGER策略。

8. Hibernate 中的懒加载

Hibernate 通过提供实体和关联的代理实现,应用懒加载策略。它通过替换实体类的实现,拦截对实体的调用,以便在控制权转交给User类实施之前,从数据库中加载缺失的信息。

还应注意,当关联表示为集合类(如上述示例中的Set<OrderDetail> orderDetailSet)时,会创建一个包装器并替换原始集合。

要了解更多关于代理设计模式,请参考此处

9. 结论

在这篇文章中,我们展示了Hibernate中使用的两种主要加载类型的例子。

对于更高级的知识,请查看Hibernate的官方网站。

要获取本文讨论的代码,请参阅此仓库