1. 概述
在使用ORM時,數據檢索/加載可以分為兩種類型:急求和延遲。
在本快速教程中,我們將指出這些差異並演示如何在Hibernate中使用它們。
2. Maven 依賴
為了使用 Hibernate,首先在我們的 pom.xml 中定義主要的依賴項:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.6.3.Final</version>
</dependency>我們可以在 這裏找到 Hibernate 的最新版本。
3. 延遲加載與即時加載
在這裏首先要討論的內容是延遲加載和即時加載的概念:
- 即時加載 是一種設計模式,其中數據初始化在 spo
- 延遲加載 是一種我們使用的設計模式,用於在可能的情況下推遲對象的初始化。
讓我們看看它如何工作。
首先,我們將查看 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
}一位用户可以擁有多個訂單詳情。在積極加載策略中,如果我們加載用户數據,它還會加載與該用户關聯的所有訂單,並將其存儲在內存中。
然而,當啓用延遲加載時,Hibernate不會在檢索UserLazy對象時初始化和加載訂單詳情數據到內存中,直到我們明確調用它。
在下一部分,我們將看到如何在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());當使用延遲初始化方法時,它僅在明確調用它時進行初始化,例如通過 getter 或其他方法:
UserLazy userLazyLoaded = users.get(3);當我們在 UserEager 中使用積極(eager)方法時,它會立即初始化 orderDetailSet:
List<UserEager> user = sessionEager.createQuery("From UserEager").list();對於惰加載,我們使用代理對象並執行單獨的 SQL 查詢來加載 orderDetailSet。
在 Hibernate 中禁用代理或使用惰加載的做法被認為是壞做法。這可能導致無論是否需要,都大量地檢索和存儲數據。
可以使用以下方法來測試功能:
Hibernate.isInitialized(orderDetailSet);現在讓我們來查看無論哪種情況下生成的查詢:
<property name="show_sql">true</property>上述在 fetching.hbm.xml 中的設置展示了生成的 SQL 查詢。如果查看控制枱輸出,也能看到生成的查詢。
對於延遲加載,以下是生成用於加載 User 數據的查詢:
SELECT user0_.USER_ID as USER_ID1_0_, ...
FROM USER user0_然而,在急緩存式中,我們使用了與 USER_ORDER 的 JOIN 操作:
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=?上述查詢針對所有 用户 進行了生成,導致內存使用量遠高於其他方法。
6. 優點與缺點
讓我們討論決定每種負載策略選擇的因素。
6.1. 延遲加載
延遲加載策略具有其優點和缺點:
| 優點 | 缺點 |
|---|---|
| 初始加載時間比其他方法小得多 | 延遲初始化相關實體可能會引入延遲,從而影響性能 |
| 內存消耗比其他方法少 | 當訪問延遲獲取的實體關聯在初始化之前訪問時,可能會導致 LazyInitializationException 異常 |
| 避免二次選擇和 N+1 查詢問題 |
6.2. 積極加載 (Eager Loading)
積極加載策略具有其優點和缺點:
| 優點 | 缺點 |
|---|---|
| 避免因延遲初始化而導致的性能影響和延遲 | 初始加載時間過長 |
| 對於使用 @ManyToOne 和 @OneToOne 關聯關係並使用 @NotFound 註解的關聯,總是使用積極加載 | 過度加載不必要的可能影響性能 |
| 當使用 EntityGraphs 功能時,所有 EntityGraphs 中的屬性都使用 EAGER 級聯加載類型 | 可能會導致二級選擇和 N+1 查詢問題,尤其當 EAGER 關聯關係沒有使用 JOIN FETCH 時 |
7. 默認的 JPA <em>FetchType</em>
正如我們之前所見,fetch 屬性可以是 <em>FetchType.LAZY</em> 或 <em>FetchType.EAGER</em>。 Jakarta Persistence 規範定義了默認情況下,<em>@OneToMany</em> 和 <em>@ManyToMany</em> 關聯使用 <em>FetchType.LAZY</em> 策略,而 <em>@OneToOne</em> 和 <em>@ManyToOne</em> 則使用 <em>FetchType.EAGER</em> 策略。
Hibernate 應用了這些默認值。但是,Hibernate 建議我們靜態地標記所有關聯為懶加載,並在需要時使用動態獲取策略來處理急切獲取。一個示例實體包含覆蓋默認的 fetch 類型:
@Entity(name = "Employee")
public class Employee {
@Id private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
@ManyToMany(mappedBy = "employees")
private List projects = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY)
private Phone phone;
// Getters and setters
}8. 懶加載在 Hibernate 中的應用
Hibernate 通過提供類代理實現,對實體和關聯採用懶加載方式
Hibernate 通過攔截對實體的調用,將其替換為從實體類派生的代理。 在我們的示例中,如果缺少請求的信息,將在控制權移交給 User 類實現之前從數據庫中加載。
我們還應注意到,當關聯表示為集合類(如上例中表示為 Set<OrderDetail> orderDetailSet)時,會創建一個包裝器並將其替換為原始集合。
要了解更多關於代理設計模式的信息,請參考 這裏。
9. 結論
在本文中,我們展示了 Hibernate 中兩種主要 Fetch 類型的事例。
如果您需要更深入的瞭解,請訪問 Hibernate 官方網站。