如何將Hibernate Proxy轉換為真實實體對象
1.概述
在本教程中,我們將學習如何將Hibernate代理轉換為真實實體對象。在此之前,我們將了解Hibernate何時創建代理對象。然後,我們將討論為什麼Hibernate代理很有用。最後,我們將模擬一個需要取消代理的場景。
2. Hibernate何時創建代理對象?
Hibernate使用代理對象來允許延遲加載。為了更好地可視化場景,讓我們看一下PaymentReceipt和Payment實體:
@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 = {PaymentReceipt@5042}
payment = {Payment$HibernateProxy$CZIczfae@5047} "com.baeldung.jpa.hibernateunproxy.CreditCardPayment@2"
$$_hibernate_interceptor = {ByteBuddyInterceptor@5053}
從調試器中,我們可以看到Hibernate正在使用Byte Buddy,這是一個用於在運行時動態生成Java類的庫。
3.為什麼Hibernate Proxy有用?
3.1。延遲加載的Hibernate代理
我們之前已經學到了一些。為了使其更具意義,讓我們嘗試從PaymentReceipt和Payment實體中刪除延遲加載機制:
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語句:一個用於檢索WebUser的SELECT語句和一個用於創建付款的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僅執行了一條語句:用於Payment
的INSERT
語句 創建。
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代理如何工作以及為什麼有用。然後,我們模擬了需要取消代理的情況。
最後,我們進行了幾次集成測試,以演示我們的示例並驗證我們的解決方案。