如何將Hibernate Proxy轉換為真實實體對象

    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並驗證paymentReceipt對像不是CreditCardPayment的實例,**而是HibernateProxy對象**。

    相反,如果沒有延遲加載,則先前的測試將失敗,因為返回的付款對象將是CreditCardPayment的實例。

    另外,值得一提的是,Hibernate正在使用字節碼檢測來創建代理對象。

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

    paymentReceipt = {[email protected]} 
     payment = {[email protected]} "[email protected]"
      $$_hibernate_interceptor = {[email protected]} 

    從調試器中,我們可以看到Hibernate正在使用Byte Buddy,這是一個用於在運行時動態生成Java類的庫。

    3.為什麼Hibernate Proxy有用?

    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的查詢包含多個join語句。

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

    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=?

    顯然,通過省略所有不必要的join語句,可以簡化生成的SQL。

    3.2。用於寫入數據的Hibernate代理

    為了說明這一點,讓我們使用它來創建Payment並為其分配給WebUser 。如果不使用代理,這將導致兩個SQL語句:一個用於檢索WebUserSELECT語句和一個用於創建付款INSERT語句。

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

    @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.場景:需要取消代理

    給定我們的域模型,讓我們假設我們正在獲取PaymentReceipt對象。眾所周知,它與一個具有Table-per-Class繼承策略和延遲加載類型的Payment實體相關聯

    在我們的示例中,根據填充的數據, PaymentReceipt的關聯Payment類型為CreditCardPayment。但是,由於我們使用的是延遲加載,因此它將是一個代理對象。

    現在,讓我們看一下CreditCardPayment實體:

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

    事實上,這是不可能的檢索cardNumber從外地CreditCardPayment類不unproxying的payment對象。無論如何,讓我們嘗試將payment對象轉換為CreditCardPayment ,看看會發生什麼:

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

    通過測試,我們看到了將payment對象轉換為CreditCardPayment 。但是,**由於payment對象仍然是一個Hibernate的代理對象,我們已經遇到了ClassCastException** 。

    5.Hibernate代理對象轉換實體對象

    從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();

    六,結論

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

    最後,我們進行了幾次集成測試,以演示我們的示例並驗證我們的解決方案。