拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 如何将Hibernate Proxy转换为真实实体对象

如何将Hibernate Proxy转换为真实实体对象

白鹭 - 2021-11-24 546 0 0

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 = {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代理

我们之前已经学到了一些。为了使其更具意义,让我们尝试从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代理如何工作以及为什么有用。然后,我们模拟了需要取消代理的情况。

最后,我们进行了几次集成测试,以演示我们的示例并验证我们的解决方案。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *