Usage of the Hibernate @LazyCollection Annotation – Hibernate @LazyCollection注解的用法

最后修改: 2021年 4月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Managing the SQL statements from our applications is one of the most important things we need to take care of because of its huge impact on performance. When working with relations between objects, there are two main design patterns for fetching. The first one is the lazy approach, while the other one is the eager approach.

管理来自我们应用程序的SQL语句是我们需要注意的最重要的事情之一,因为它对性能有着巨大的影响。在处理对象之间的关系时,有两种主要的设计模式用于获取。第一种是懒惰的方法,而另一种是急切的方法。

In this article, we’ll take an overview of both of them. In addition, we’ll discuss the @LazyCollection annotation in Hibernate.

在这篇文章中,我们将对这两者做一个概述。此外,我们将讨论Hibernate中的@LazyCollectionannotation。

2. Lazy Fetching

2.懒人取物

We use lazy fetching when we want to postpone the data initialization until we need it. Let’s look at an example to better understand the idea.

当我们希望将数据初始化推迟到需要的时候,我们使用lazy fetching。让我们看一个例子来更好地理解这个想法。

Suppose we have a company that has multiple branches in the city. Every branch has its own employees. From the database perspective, it means we have a one-to-many relation between the branch and its employees.

假设我们有一家公司,在城市中有多个分支机构。每个分支机构都有自己的员工。从数据库的角度来看,这意味着我们在分公司和其员工之间有一个一对多的关系。

In the lazy fetching approach, we won’t fetch the employees once we fetch the branch object. We only fetch the data of the branch object, and we postpone loading the list of employees until we call the getEmployees() method. At that point, another database query will be executed to get the employees.

在懒惰获取的方法中,一旦我们获取了分支对象,我们就不会获取雇员。我们只获取分支对象的数据,并推迟加载雇员列表,直到我们调用getEmployees()方法。这时,将执行另一个数据库查询来获取雇员。

The benefit of this approach is that we reduce the amount of data loaded initially. The reason is that we might not need the employees of the branch, and there’s no point in loading them since we aren’t planning to use them right away.

这种方法的好处是,我们减少了最初加载的数据量。原因是我们可能不需要该分支的员工,而且由于我们不打算立即使用他们,所以加载他们没有任何意义。

3. Eager Fetching

3.急于取物

We use eager fetching when the data needs to be loaded instantly. Let’s take the same example of the company, branches, and the employees to explain this idea as well. Once we load some branch object from the database, we’ll immediately load the list of its employees as well using the same database query.

当数据需要即时加载时,我们会使用急切获取。让我们以公司、分支机构和员工为例来解释这个想法。一旦我们从数据库中加载某个分支对象,我们将立即使用相同的数据库查询加载其雇员列表。

The main concern when using the eager fetching is that we load a huge amount of data that might not be needed. Hence, we should only use it when we’re sure that the eagerly fetched data will always be used once we load its object.

使用急切获取时的主要顾虑是,我们会加载大量可能不需要的数据。因此,我们应该只在确定一旦我们加载其对象,急切获取的数据将一直被使用时才使用它。

4. The @LazyCollection Annotation

4.@LazyCollection注释

We use the @LazyCollection annotation when we need to take care of the performance in our application. Starting from Hibernate 3.0, @LazyCollection is enabled by default. The main idea of using the @LazyCollection is to control whether the fetching of data should be using the lazy approach or the eager one.

当我们需要照顾到应用程序的性能时,我们会使用@LazyCollection注解。从Hibernate 3.0开始,@LazyCollection被默认启用。使用@LazyCollection的主要想法是控制数据的获取应该使用懒惰的方式还是急切的方式。

When using @LazyCollection, we have three configuration options for the LazyCollectionOption setting: TRUE, FALSE, and EXTRA. Let’s discuss each of them independently.

当使用 @LazyCollection时,我们对LazyCollectionOption设置有三个配置选项。TRUE, FALSE, 和EXTRA。让我们独立地讨论它们中的每一个。

4.1. Using LazyCollectionOption.TRUE

4.1.使用LazyCollectionOption.TRUE

This option enables the lazy fetching approach for the specified field and is the default starting from Hibernate version 3.0. Therefore, we don’t need to explicitly set this option. However, to explain the idea in a better way, we’ll take an example where we set this option.

这个选项为指定的字段启用了懒惰获取方法,并且从Hibernate 3.0版本开始是默认的。因此,我们不需要明确设置这个选项。然而,为了更好地解释这个想法,我们将举一个例子来说明,我们设置了这个选项。

In this example, we have a Branch entity that consists of an id, name, and a @OneToMany relation to the Employee entity. We can notice that we set the @LazyCollection option explicitly to true in this example:

在这个例子中,我们有一个Branch实体,它由idname@OneToManyEmployee实体的关系组成。我们可以注意到,在这个例子中,我们将@LazyCollection选项明确设置为true

@Entity
public class Branch {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "branch")
    @LazyCollection(LazyCollectionOption.TRUE)
    private List<Employee> employees;
    
    // getters and setters
}

Now, let’s take a look at the Employee entity that consists of an id, name, address, as well as a @ManyToOne relation with the Branch entity:

现在,让我们看看Employee实体,它由idnameaddress以及与Branch实体的@ManyToOne关系组成。

@Entity
public class Employee {

    @Id
    private Long id;

    private String name;

    private String address;
    
    @ManyToOne
    @JoinColumn(name = "BRANCH_ID") 
    private Branch branch; 

    // getters and setters 
}

In the above example, when we get a branch object, we won’t load the list of employees immediately. Instead, this operation will be postponed until we call the getEmployees() method.

在上面的例子中,当我们得到一个分支对象时,我们不会立即加载雇员列表。相反,这个操作将被推迟到我们调用getEmployees()方法。

4.2. Using LazyCollectionOption.FALSE

4.2.使用LazyCollectionOption.FALSE

When we set this option to FALSE, we enable the eager fetching approach. In this case, we need to explicitly specify this option because we’ll be overriding Hibernate’s default value. Let’s look at another example.

当我们把这个选项设置为FALSE时,我们就会启用急切的获取方式。在这种情况下,我们需要明确指定这个选项,因为我们将覆盖Hibernate的默认值。让我们看看另一个例子。

In this case, we have the Branch entity, which contains id, name, and a @OneToMany relation with the Employee entity. Note that we set the option of @LazyCollection to FALSE:

在这种情况下,我们有Branch实体,它包含idname,以及与Employee实体的@OneToMany关系。请注意,我们将@LazyCollection的选项设置为FALSE

@Entity
public class Branch {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "branch")
    @LazyCollection(LazyCollectionOption.FALSE)
    private List<Employee> employees;
    
    // getters and setters
}

In the above example, when we get a branch object, we’ll load the branch with the list of employees instantly.

在上面的例子中,当我们得到一个分支对象时,我们将立即加载带有雇员列表的分支

4.3. Using LazyCollectionOption.EXTRA

4.3.使用LazyCollectionOption.EXTRA

Sometimes, we’re only concerned with the properties of the collection, and we don’t need the objects inside it right away.

有时,我们只关心集合的属性,而不马上需要里面的对象。

For example, going back to the Branch and the Employees example, we could just need the number of employees in the branch while not caring about the actual employees’ entities. In this case, we consider using the EXTRA option. Let’s update our example to handle this case.

例如,回到BranchEmployees的例子,我们可能只需要分支机构的员工数量,而不关心实际员工的实体。在这种情况下,我们考虑使用EXTRA选项。让我们更新我们的例子来处理这种情况。

Similar to the case before, the Branch entity has an id, name, and an @OneToMany relation with the Employee entity. However, we set the option for @LazyCollection to be EXTRA:

与之前的情况类似,Branch实体有一个idname,以及与Employee实体的@OneToMany关系。然而,我们将@LazyCollection的选项设置为EXTRA

@Entity
public class Branch {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "branch")
    @LazyCollection(LazyCollectionOption.EXTRA)
    @OrderColumn(name = "order_id")
    private List<Employee> employees;

    // getters and setters
    
    public Branch addEmployee(Employee employee) {
        employees.add(employee);
        employee.setBranch(this);
        return this;
    }
}

We notice that we used the @OrderColumn annotation in this case. The reason is that the EXTRA option is taken into consideration only for indexed list collections. That’s means if we didn’t annotate the field with @OrderColumn, the EXTRA option will give us the same behavior as lazy and the collection will be fetched when accessed for the first time.

我们注意到,在这种情况下我们使用了@OrderColumn 注释。原因是,EXTRA选项只考虑到了索引列表集合。这意味着如果我们没有用@OrderColumn注释字段,EXTRA选项将给我们带来与懒惰相同的行为,当第一次访问集合时,集合将被取走。

In addition, we define the addEmployee() method as well, because we need the Branch and the Employee to be synchronized from both sides. If we add a new Employee and set a branch for him, we need the list of employees inside the Branch entity to be updated as well.

此外,我们也定义了addEmployee() 方法,因为我们需要BranchEmployee从两边同步进行。如果我们添加一个新的Employee并为他设置一个分支,我们需要Branch实体内的雇员列表也被更新。

Now, when persisting one Branch entity that has three associated employees, we’ll need to write the code as:

现在,当持久化一个有三个相关雇员的Branch实体时,我们需要将代码写成。

entityManager.persist(
  new Branch().setId(1L).setName("Branch-1")

    .addEmployee(
      new Employee()
        .setId(1L)
        .setName("Employee-1")
        .setAddress("Employee-1 address"))
  
    .addEmployee(
      new Employee()
        .setId(2L)
        .setName("Employee-2")
        .setAddress("Employee-2 address"))
  
    .addEmployee(
      new Employee()
        .setId(3L)
        .setName("Employee-3")
        .setAddress("Employee-3 address"))
);

If we take a look at the executed queries, we’ll notice that Hibernate will insert a new Branch for Branch-1 first. Then it will insert Employee-1, Employee-2, then Employee-3.

如果我们看一下执行的查询,我们会注意到Hibernate将首先为Branch-1插入一个新的Branch。然后,它将插入Employee-1、Employee-2,然后是Employee-3。

We can see that this is a natural behavior. However, the bad behavior in the EXTRA option is that after flushing the above queries, it’ll execute three additional ones – one for every Employee we add:

我们可以看到,这是一个自然的行为。然而,在EXTRA 选项中的不良行为是,在冲洗上述查询后,它将执行三个额外的查询–我们每增加一个Employee就有一个:

UPDATE EMPLOYEES
SET
    order_id = 0
WHERE
    id = 1
     
UPDATE EMPLOYEES
SET
    order_id = 1
WHERE
    id = 2
 
UPDATE EMPLOYEES
SET
    order_id = 2
WHERE
    id = 3

The UPDATE statements are executed to set the List entry index. This is an example of what’s known as the N+1 query issue, which means that we execute N additional SQL statements to update the same data we created.

执行UPDATE语句是为了设置List条目索引。这是一个被称为N+1查询问题的例子,这意味着我们执行N额外的SQL语句来更新我们创建的相同数据。

As we noticed from our example, we might have the N+1 query issue when using the EXTRA option.

正如我们从例子中注意到的,当使用EXTRA选项时,我们可能会出现N+1的查询问题。

On the other hand, the advantage of using this option is when we need to get the size of the list of employees for every branch:

另一方面,使用这个选项的好处是当我们需要得到每个分支机构的雇员名单的大小时。

int employeesCount = branch.getEmployees().size();

When we call this statement, it’ll only execute this SQL statement:

当我们调用这个语句时,它将只执行这个SQL语句。

SELECT
    COUNT(ID)
FROM
    EMPLOYEES
WHERE
    BRANCH_ID = :ID

As we can see, we didn’t need to store the employees’ list in memory to get its size. Nevertheless, we advise avoiding the EXTRA option because it’ll execute additional queries.

正如我们所看到的,我们不需要在内存中存储雇员名单来获得其大小。尽管如此,我们建议避免使用EXTRA选项,因为它将执行额外的查询。

It’s also worth noting here that it’s possible to encounter the N+1 query issue with other data access technologies, as it isn’t only restricted to JPA and Hibernate.

这里还值得注意的是,在其他数据访问技术中也有可能遇到N+1的查询问题,因为它不仅限于JPA和Hibernate。

5. Conclusion

5.总结

In this article, we discussed the different approaches to fetching an object’s properties from the database using Hibernate.

在这篇文章中,我们讨论了使用Hibernate从数据库获取对象属性的不同方法。

First, we discussed lazy fetching with an example. Then, we updated the example to use eager fetching and discussed the differences.

首先,我们用一个例子讨论了懒惰获取的问题。然后,我们更新了这个例子,使用了急切获取,并讨论了其中的区别。

Finally, we showed an extra approach to fetching the data and explained its advantages and disadvantages.

最后,我们展示了一种额外的获取数据的方法,并解释了其优势和劣势。

As always, the code presented in this article is available over on GitHub.

一如既往,本文介绍的代码可在GitHub上获得over