1.概述
从我们的应用程序管理SQL语句是我们需要照顾的最重要的事情之一,因为它对性能有巨大的影响。处理对象之间的关系时,有两种主要的设计模式可用于获取。第一个是懒惰的方法,而另一个是急切的方法。
在本文中,我们将对两者进行概述。另外,我们将在Hibernate中@LazyCollection
2.延迟获取
当我们想推迟数据初始化直到需要它时,我们使用延迟获取。让我们看一个例子,以更好地理解这个想法。
假设我们有一家在城市拥有多个分支机构的公司。每个分支机构都有自己的员工。从数据库的角度来看,这意味着我们与分支机构及其员工之间存在一对多的关系。
在惰性获取方法中,一旦获取分支对象,我们就不会获取员工。我们仅获取分支对象的数据,并且推迟加载僱员列表,直到调用getEmployees()
方法为止。届时,将执行另一个数据库查询以获取员工。
这种方法的好处是我们减少了最初加载的数据量。原因是我们可能不需要分支机构的员工,并且加载它们没有意义,因为我们不打算立即使用它们。
3.预先获取
当数据需要立即加载时,我们会使用预先获取的方式。让我们以公司,分支机构和员工的相同示例来解释这个想法。一旦从数据库中加载了一些分支对象,我们将立即使用相同的数据库查询来加载其僱员列表。
使用紧急获取时的主要问题是,我们加载了可能不需要的大量数据。因此,只有在确定一旦加载对象便始终使用急切获取的数据时,才应使用它。
4. @LazyCollection
批注
当我们需要照顾应用程序的性能时,可以使用@LazyCollection
从Hibernate 3.0开始,默认情况下启用@LazyCollection
@LazyCollection
的主要思想是控制是否应该使用延迟获取方法还是预先获取的方法来获取数据。
使用@LazyCollection
LazyCollectionOption
设置提供了三个配置选项TRUE
, FALSE
和EXTRA
。让我们分别讨论它们。
4.1 使用LazyCollectionOption.TRUE
该选项为指定的字段启用了延迟获取方法,并且是从Hibernate版本3.0开始的默认选项。因此,我们不需要显式设置此选项。但是,为了更好地解释该想法,我们将以设置此选项为例。
在此示例中,我们有一个Branch
实体,该实体由id
, name
和Employee
实体@OneToMany
关系组成。我们可以注意到,在此示例中@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
}
现在,让我们看一下Employee
实体, id
, name
, address
以及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
}
在上面的示例中,当我们获得分支对象时,我们不会立即加载employee列表。相反,此操作将推迟到我们调用getEmployees()
方法之前。
4.2 使用LazyCollectionOption.FALSE
当我们将此选项设置为FALSE
,我们启用了渴望的获取方法。在这种情况下,我们需要显式指定此选项,因为我们将覆盖Hibernate的默认值。让我们看另一个例子。
在这种情况下,我们具有Branch
实体,该实体包含id
, name
和与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
}
在上面的示例中,当我们获得分支对象时,我们会立即将僱员列表加载到分支中。
4.3 使用LazyCollectionOption.EXTRA
有时,我们只关心集合的属性,而无需立即使用其中的对象。
例如,回到Branch
和Employee
的示例,我们可能只需要分支中的僱员数,而无需关心实际僱员的实体。在这种情况下,我们考虑使用EXTRA
选项。让我们更新示例以处理这种情况。
与之前的情况类似, Branch
实体与Employee
实体id
, name
和@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;
}
}
我们注意到在这种情况下,我们使用了@OrderColumn
原因是只有索引列表集合才考虑EXTRA
这意味着如果我们不使用@OrderColumn,
注释字段,则EXTRA
选项将为我们提供与lazy相同的行为,并且在首次访问该集合时将对其进行提取。
另外,我们还定义了addEmployee()
方法,因为我们需要让Branch
和Employee
从两侧同步。如果添加新Employee
并为其设置分支机构,则还需要更新Branch
现在,当持久保留一个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"))
);
如果我们看一下已执行的查询,我们会注意到Hibernate将首先为Branch-1 Branch
然后它将插入Employee-1,Employee-2,然后是Employee-3。
我们可以看到这是自然的行为。 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
UPDATE
语句以设置List
条目索引。这是所谓的N
+1查询问题的一个示例,这意味着我们执行N
其他SQL语句来更新我们创建的相同数据。
从示例中我们注意到, EXTRA
选项N
+1查询问题。
另一方面,使用此选项的好处是当我们需要获取每个分支的僱员列表的大小时:
int employeesCount = branch.getEmployees().size();
当我们调用此语句时,它将仅执行以下SQL语句:
SELECT
COUNT(ID)
FROM
EMPLOYEES
WHERE
BRANCH_ID = :ID
如我们所见,我们不需要将员工列表存储在内存中即可获取其大小。不过,我们建议避免使用EXTRA
选项,因为它会执行其他查询。
在这里还值得注意的是N
+1查询问题,因为它不仅限于JPA和Hibernate。
5.结论
在本文中,我们讨论了使用Hibernate从数据库中获取对象属性的不同方法。
首先,我们以一个示例讨论了延迟获取。然后,我们更新了示例以使用渴望获取并讨论了差异。
最后,我们展示了一种获取数据的额外方法,并说明了其优缺点。
0 评论