Hibernate中的急切/延迟加载

2023/05/18

1. 概述

使用ORM时,数据获取/加载可以分为两种类型:急切和惰性。

在这个快速教程中,我们将指出它们的差异并展示我们如何在Hibernate中使用它们。

2. Maven依赖

为了使用Hibernate,让首先在我们的pom.xml中定义主要依赖:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>   
    <version>5.2.2.Final</version>
</dependency>

可以在此处找到最新版本的Hibernate。

3. 急切和延迟加载

我们在这里首先要讨论的是什么是延迟加载和急切加载:

  • 急切加载是一种设计模式,其中数据初始化发生在现场。
  • 延迟加载是一种设计模式,我们使用它来尽可能推迟对象的初始化。

让我们看看这是如何工作的。

首先,我们来看看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
}

一个用户可以有多个OrderDetails。在急切加载策略中,如果我们加载用户数据,它也会加载与其关联的所有订单并将其存储在内存中

但是当我们启用延迟加载时,如果我们获取UserLazy,OrderDetail数据将不会被初始化并加载到内存中,直到我们对其进行显式调用。

在下一节中,我们将看到如何在Hibernate中实现该示例。

4. 加载配置

让我们看看如何在Hibernate中配置获取策略。

我们可以使用这个注解参数启用延迟加载:

fetch = FetchType.LAZY

对于急切加载,我们使用这个参数:

fetch = FetchType.EAGER

为了设置急切加载,我们使用了UserLazy的孪生类UserEager。

在下一节中,我们将研究两种获取类型之间的区别。

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=?

上面的查询是为所有Users生成的,这会导致比其他方法更多的内存使用。

6. 优缺点

6.1 延迟加载

优点:

  • 与其他方法相比,初始加载时间短得多
  • 与其他方法相比,内存消耗更少

缺点:

  • 延迟的初始化可能会在不需要的时刻影响性能
  • 在某些情况下,我们需要特别小心地处理延迟初始化的对象,否则我们最终可能会得到异常

6.2 急切加载

优点:

  • 没有延迟初始化相关的性能影响

缺点:

  • 初始加载时间长
  • 加载太多不必要的数据可能会影响性能

7. Hibernate中的延迟加载

Hibernate通过提供类的代理实现对实体和关联应用延迟加载方法

Hibernate通过用派生自实体类的代理替换它来拦截对实体的调用。在我们的示例中,在将控制权交给User类实现之前,将从数据库加载缺少的请求信息。

我们还应该注意,当关联表示为集合类时(在上面的示例中,它表示为Set<OrderDetail> orderDetailSet),将创建一个包装器并替换原始集合。

要了解有关代理设计模式的更多信息,请参阅此处

8. 总结

在本文中,我们展示了Hibernate中使用的两种主要类型数据获取的示例。

有关高级专业知识,请查看Hibernate的官方网站。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章