Hibernate @LazyCollection批註的用法
- Hibernate
- JPA
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從數據庫中獲取對象屬性的不同方法。
首先,我們以一個示例討論了延遲獲取。然後,我們更新了示例以使用渴望獲取並討論了差異。
最後,我們展示了一種獲取數據的額外方法,並說明了其優缺點。