Contents

Hibernate 中Fetchmode

1. 简介

在这个简短的教程中,我们将看看我们可以在*@* org.hibernate.annotations.Fetch注解中使用的不同FetchMode值。

2. 设置示例

作为示例,我们将使用以下仅具有两个属性的Customer实体——一个 id 和一组订单:

@Entity
public class Customer {
    @Id
    @GeneratedValue
    private Long id;
    @OneToMany(mappedBy = "customer")
    @Fetch(value = FetchMode.SELECT)
    private Set<Order> orders = new HashSet<>();
    // getters and setters
}

此外,我们将创建一个由 id、名称和对Customer的引用组成的Order实体。

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
    // getters and setters
}

在接下来的每一节中,我们将从数据库中获取客户并获取其所有订单:

Customer customer = customerRepository.findById(id).get();
Set<Order> orders = customer.getOrders();

3. FetchMode.SELECT

在我们的Customer实体上,我们使用*@Fetch注解对orders*属性进行了注解:

@OneToMany
@Fetch(FetchMode.SELECT)
private Set<Orders> orders;

我们使用 @Fetch来描述当我们查找Customer时 Hibernate 应该如何检索属性。

使用SELECT表示应该延迟加载属性。

这意味着对于第一行:

Customer customer = customerRepository.findById(id).get();

我们不会看到与订单表的连接:

Hibernate: 
    select ...from customer
    where customer0_.id=?

下一行:

Set<Order> orders = customer.getOrders();

我们将看到相关订单的后续查询:

Hibernate: 
    select ...from order
    where order0_.customer_id=?

Hibernate FetchMode.SELECT为每个需要加载的Order生成一个单独的查询。

在我们的示例中,提供了一个查询来加载客户和五个额外的查询来加载订单集合。

**这被称为n + 1 选择问题。**执行一个查询将触发n 个额外的查询。

3.1. @BatchSize

FetchMode.SELECT有一个使用*@BatchSize*注解的可选配置注解:

@OneToMany
@Fetch(FetchMode.SELECT)
@BatchSize(size=10)
private Set<Orders> orders;

Hibernate将尝试按size参数定义的批量加载订单集合。

在我们的示例中,我们只有五个订单,因此一个查询就足够了。

我们仍将使用相同的查询:

Hibernate:
    select ...from order
    where order0_.customer_id=?

**但它只会运行一次。**现在我们只有两个查询:一个加载Customer,一个加载订单集合。

4. FetchMode.JOIN

虽然FetchMode.SELECT延迟加载关系,但 FetchMode.JOIN急切地加载它们,例如通过连接:

@OneToMany
@Fetch(FetchMode.JOIN)
private Set<Orders> orders;

这导致对Customer和他们的Order的查询只有一个:

Hibernate: 
    select ...
    from
        customer customer0_ 
    left outer join
        order order1 
            on customer.id=order.customer_id 
    where
        customer.id=?

5. FetchMode.SUBSELECT

因为orders属性是一个集合,我们也可以使用 FetchMode.SUBSELECT

@OneToMany
@Fetch(FetchMode.SUBSELECT)
private Set<Orders> orders;

我们只能将SUBSELECT与集合一起使用。

使用此设置,我们返回到Customer 的一个查询:

Hibernate: 
    select ...
    from customer customer0_

并且一次查询Order,这次使用子选择:

Hibernate: 
    select ...
    from
        order order0_ 
    where
        order0_.customer_id in (
            select
                customer0_.id 
            from
                customer customer0_
        )

6. FetchModeFetchType

一般来说,FetchMode定义了Hibernate将如何获取数据(通过选择、连接或子选择)。另一方面, FetchType定义了 Hibernate 是急切地还是延迟地加载数据。

这两者之间的确切规则如下:

  • 如果代码未设置FetchMode,则默认为JOIN并且FetchType按定义工作
  • 设置了FetchMode.SELECTFetchMode.SUBSELECTFetchType也可以按定义工作
  • 设置了FetchMode.JOINFetchType被忽略并且查询总是急切的

有关详细信息,请参阅Hibernate 中的 Eager/Lazy Loading