知識庫 / Spring RSS 訂閱

將 Hibernate 代理轉換為真實實體對象指南

Persistence,Spring
HongKong
6
12:43 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將學習如何將 Hibernate 代理對象轉換為真實的實體對象。在此之前,我們將瞭解 Hibernate 在何處創建代理對象。然後,我們將討論 Hibernate 代理對象為什麼有用。最後,我們將模擬一個需要取消代理對象的情況。

2. Hibernate 何時創建代理對象?

Hibernate 使用代理對象來實現延遲加載。 為了更好地理解場景,我們來看 PaymentReceiptPayment 實體:

@Entity
public class PaymentReceipt {
    ...
    @OneToOne(fetch = FetchType.LAZY)
    private Payment payment;
    ...
}
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Payment {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    protected WebUser webUser;
    ...
}

例如,加載這兩個實體會導致Hibernate 為關聯字段創建代理對象,且使用 FetchType.LAZY 延遲加載。

為了演示,我們創建一個並運行一個集成測試:

@Test
public void givenPaymentReceipt_whenAccessingPayment_thenVerifyType() {
    PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L);
    Assert.assertTrue(paymentReceipt.getPayment() instanceof HibernateProxy);
}

從測試中,我們加載了一個 PaymentReceipt 對象,並驗證 支付對象 不是 CreditCardPayment 實例 —— 它是一個 HibernateProxy 對象

與之相反,在沒有啓用延遲加載的情況下,之前的測試將會失敗,因為返回的 支付對象 將是 CreditCardPayment 實例。

此外,值得注意的是,Hibernate 使用字節碼 插樁 (instrumentation) 來創建代理對象。

為了驗證這一點,我們可以為集成測試的斷言語句添加斷點,然後在調試模式下運行它。現在,讓我們看看調試器顯示的內容:

paymentReceipt = {PaymentReceipt@5042} 
 payment = {Payment$HibernateProxy$CZIczfae@5047} "com.baeldung.jpa.hibernateunproxy.CreditCardPayment@2"
  $$_hibernate_interceptor = {ByteBuddyInterceptor@5053} 

從調試器中可以看出,Hibernate 正在使用 Byte Buddy,它是一個在運行時動態生成 Java 類庫。

3. 為什麼 Hibernate 代理有用?

Hibernate 代理提供了一系列優勢,使其成為 Hibernate 應用程序中常用的模式。以下是一些關鍵原因:

  • 避免對象圖層 (Object Graph) 的加載: 代理只在需要時才加載實際的持久化對象。這意味着只有在查詢結果需要被立即使用時,才會創建和加載這些對象。對於大型對象圖層,這可以顯著減少內存使用量和數據庫訪問次數。

  • 提高性能: 通過延遲加載,代理可以避免不必要的對象加載,從而提高應用程序的性能。

  • 簡化對象圖層管理: 代理可以幫助管理對象圖層,例如,它可以處理對象之間的依賴關係,並確保對象在被垃圾回收之前被正確地釋放。

  • 支持多種持久化策略: 代理可以與各種持久化策略一起使用,例如,它可以與緩存、事務管理和數據驗證一起使用。

  • 提供更好的可測試性: 代理可以用於模擬持久化對象,從而更容易地對應用程序進行單元測試。

以下是一個簡單的 Java 代碼示例,演示瞭如何使用 Hibernate 代理:

// 假設的 Java 代碼示例
public class User {
    private String name;

    public String getName() {
        return name;
    }
}

// 使用 Hibernate 代理的示例
public class UserProxy {
    private User user;

    public User getUser() {
        // 在這裏可以添加邏輯來加載真實的 User 對象
        // 例如,使用 Hibernate Session 獲取 User 對象
        return user;
    }
}

3.1. 使用 Hibernate 代理進行延遲加載

我們之前已經對這個機制進行了初步瞭解。為了進一步強調其重要性,我們嘗試從 PaymentReceiptPayment 實體中移除延遲加載機制:

public class PaymentReceipt {
    ...
    @OneToOne
    private Payment payment;
    ...
}
public abstract class Payment {
    ...
    @ManyToOne
    protected WebUser webUser;
    ...
}

現在,我們快速檢索一份 PaymentReceipt 並檢查日誌中生成的 SQL:

select
    paymentrec0_.id as id1_2_0_,
    paymentrec0_.payment_id as payment_3_2_0_,
    paymentrec0_.transactionNumber as transact2_2_0_,
    payment1_.id as id1_1_1_,
    payment1_.amount as amount2_1_1_,
    payment1_.webUser_id as webuser_3_1_1_,
    payment1_.cardNumber as cardnumb1_0_1_,
    payment1_.clazz_ as clazz_1_,
    webuser2_.id as id1_3_2_,
    webuser2_.name as name2_3_2_ 
from
    PaymentReceipt paymentrec0_ 
left outer join
    (
        select
            id,
            amount,
            webUser_id,
            cardNumber,
            1 as clazz_ 
        from
            CreditCardPayment 
    ) payment1_ 
        on paymentrec0_.payment_id=payment1_.id 
left outer join
    WebUser webuser2_ 
        on payment1_.webUser_id=webuser2_.id 
where
    paymentrec0_.id=?

如我們從日誌中可以看出,查詢語句 PaymentReceipt 包含多個連接語句。

現在,讓我們在啓用延遲加載的情況下運行它:

select
    paymentrec0_.id as id1_2_0_,
    paymentrec0_.payment_id as payment_3_2_0_,
    paymentrec0_.transactionNumber as transact2_2_0_ 
from
    PaymentReceipt paymentrec0_ 
where
    paymentrec0_.id=?

顯然,生成的 SQL 語句已經通過省略所有不必要的連接語句進行了簡化。

3.2. 使用 Hibernate 代理進行數據寫入

為了説明,我們使用它來創建一個 Payment 對象併為其分配一個 WebUser 對象。 不使用代理的情況下,這將導致兩個 SQL 語句:一個 SELECT 語句用於檢索 WebUser 對象,以及一個 INSERT 語句用於創建 Payment 對象。

讓我們使用代理創建一個測試:

@Test
public void givenWebUserProxy_whenCreatingPayment_thenExecuteSingleStatement() {
    entityManager.getTransaction().begin();

    WebUser webUser = entityManager.getReference(WebUser.class, 1L);
    Payment payment = new CreditCardPayment(new BigDecimal(100), webUser, "CN-1234");
    entityManager.persist(payment);

    entityManager.getTransaction().commit();
    Assert.assertTrue(webUser instanceof HibernateProxy);
}

值得注意的是,我們使用 entityManager.getReference(…) 獲取代理對象。

接下來,讓我們運行測試並檢查日誌:

insert 
into
    CreditCardPayment
    (amount, webUser_id, cardNumber, id) 
values
    (?, ?, ?, ?)

在這裏,我們可以看到,當使用代理時,Hibernate僅執行了一條語句:一個用於創建PaymentINSERT語句。

4. 場景:無需反向代理的需求

鑑於我們的領域模型,假設我們正在檢索一個 支付憑證。 就像我們已經知道的,它與一個 支付 實體相關聯,該實體具有 按類表 的繼承策略和延遲獲取類型。

在我們的案例中,基於填充的數據,與 支付憑證 相關的 支付 實體是 信用卡支付 類型。 然而,由於我們使用了延遲加載,它將是一個代理對象。

現在,讓我們來看一下 信用卡支付 實體:

@Entity
public class CreditCardPayment extends Payment {
    
    private String cardNumber;
    ...
}

確實,無法從CreditCardNumber字段中檢索,而不必先反向代理Payment對象。 無論如何,我們嘗試將Payment對象轉換為CreditCardPayment對象,看看會發生什麼:

@Test
public void givenPaymentReceipt_whenCastingPaymentToConcreteClass_thenThrowClassCastException() {
    PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L);
    assertThrows(ClassCastException.class, () -> {
        CreditCardPayment creditCardPayment = (CreditCardPayment) paymentReceipt.getPayment();
    });
}

從測試中,我們發現需要將支付對象轉換為 CreditCardPayment 對象。然而,由於支付對象仍然是一個 Hibernate 代理對象,我們遇到了 ClassCastException。

5. 代理對象到實體對象

自 Hibernate 5.2.10 版本起,我們可以使用內置的靜態方法來取消代理 Hibernate 實體:

Hibernate.unproxy(paymentReceipt.getPayment());

讓我們使用這種方法創建一個最終集成測試:

@Test
public void givenPaymentReceipt_whenPaymentIsUnproxied_thenReturnRealEntityObject() {
    PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L);
    Assert.assertTrue(Hibernate.unproxy(paymentReceipt.getPayment()) instanceof CreditCardPayment);
}

從測試結果來看,我們已成功地將一個 Hibernate 代理對象轉換為真實的實體對象。

另一方面,在 Hibernate 5.2.10 之前,以下是解決方案:

HibernateProxy hibernateProxy = (HibernateProxy) paymentReceipt.getPayment();
LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer();
CreditCardPayment unproxiedEntity = (CreditCardPayment) initializer.getImplementation();

6. 結論

在本教程中,我們學習瞭如何將 Hibernate 代理轉換為真實的實體對象。此外,我們還討論了 Hibernate 代理的工作原理以及其優勢。然後,我們模擬了需要取消代理對象的情況。

最後,我們運行了多個集成測試,以展示我們的示例並驗證我們的解決方案。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.