How to Convert a Hibernate Proxy to a Real Entity Object – 如何将Hibernate代理转换为真正的实体对象

最后修改: 2021年 1月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn how to convert a Hibernate proxy to a real entity object. Before that, we’ll understand when Hibernate creates a proxy object. Then, we’ll talk about why Hibernate proxy is useful. And finally, we’ll simulate a scenario where there’s a need to un proxy an object.

在本教程中,我们将学习如何将Hibernate代理转换为一个真正的实体对象。在此之前,我们将了解Hibernate何时创建一个代理对象。然后,我们将讨论为什么Hibernate代理是有用的。最后,我们将模拟一个需要解除代理对象的场景。

2. When Does Hibernate Create a Proxy Object?

2.Hibernate何时创建一个代理对象?

Hibernate uses proxy objects to allow lazy loading. To better visualize the scenario, let’s look at the PaymentReceipt and Payment entities:

Hibernate使用代理对象来允许lazy loading.为了更好地理解这一场景,我们来看看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;
    ...
}

For instance, loading either of these entities will result in Hibernate creating a proxy object for the associated field with FetchType.LAZY.

例如,加载这些实体中的任何一个都会导致Hibernate为相关的字段创建一个代理对象,并带有FetchType.LAZY

To demonstrate, let’s create and run an integration test:

为了演示,让我们创建并运行一个集成测试。

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

From the test, we’ve loaded a PaymentReceipt and verified that the payment object isn’t an instance of CreditCardPaymentit’s a HibernateProxy object.

从测试中,我们已经加载了一个PaymentReceipt,并验证了这个payment对象不是CreditCardPayment的实例 – 它是一个HibernateProxy对象

In contrast, without lazy loading, the previous test would fail as the returned payment object would be an instance of CreditCardPayment.

相反,如果没有懒惰加载,前面的测试将失败,因为返回的payment对象将是CreditCardPayment的实例。

Additionally, it’s worth mentioning that Hibernate is using bytecode instrumentation to create a proxy object.

另外,值得一提的是,Hibernate正在使用字节码instrumentation来创建一个代理对象。

To verify this, we can add a breakpoint on the line of the integration test’s assertion statement and run it in debug mode. Now, let’s see what the debugger shows:

为了验证这一点,我们可以在集成测试的断言语句行上添加一个断点,并在调试模式下运行它。现在,让我们看看调试器显示了什么。

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

From the debugger, we can see that Hibernate is using Byte Buddy, which is a library for generating Java classes dynamically at run-time.

从调试器中,我们可以看到Hibernate正在使用Byte Buddy,它是一个用于在运行时动态生成Java类的库。

3. Why Is Hibernate Proxy Useful?

3.为什么Hibernate Proxy有用?

3.1. Hibernate Proxy for Lazy Loading

3.1.懒惰加载的Hibernate代理

We’ve learned a bit about this earlier. To give more significance to it, let’s try removing the lazy loading mechanism from both PaymentReceipt and Payment entities:

我们在前面已经了解了一些关于这一点。为了赋予它更多的意义,让我们尝试从PaymentReceiptPayment实体中移除懒惰加载机制。

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

Now, let’s quickly retrieve a PaymentReceipt and check the generated SQL from the logs:

现在,让我们快速检索一个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=?

As we can see from the logs, the query for the PaymentReceipt contains multiple join statements. 

我们可以从日志中看到,PaymentReceipt包含多个连接语句。

Now, let’s run it with lazy loading in place:

现在,让我们在懒惰加载的情况下运行它。

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

Clearly, the generated SQL is simplified by omitting all the unnecessary join statements.

很明显,通过省略所有不必要的连接语句,生成的SQL被简化。

3.2. Hibernate Proxy for Writing Data

3.2.用于写入数据的Hibernate代理</b

To illustrate, let’s use it for creating a Payment and assigning a WebUser to it. Without using a proxy, this would result in two SQL statements: a SELECT statement to retrieve the WebUser and an INSERT statement for Payment creation.

为了说明这一点,让我们使用它来创建一个Payment并为其分配一个WebUser。如果不使用代理,这将导致两个SQL语句:一个SELECT语句来检索WebUser,一个INSERT语句用于Payment的创建。

Let’s create a test using the proxy:

让我们使用代理创建一个测试。

@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);
}

It’s worth highlighting that we’re using entityManager.getReference(…) to obtain a proxy object.

值得强调的是,我们使用entityManager.getReference(…)来获取代理对象。

Next, let’s run the test and check the logs:

接下来,让我们运行测试并检查日志。

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

Here, we can see that, when using the proxy, Hibernate only executed a single statement: an INSERT statement for Payment creation.

在这里,我们可以看到,在使用代理时,Hibernate只执行了一条语句。INSERT 语句,用于Payment创建。

4. Scenario: the Need for Unproxying

4.情景:取消代理的必要性

Given our domain model, let’s suppose we’re retrieving a PaymentReceipt. As we already know, it’s associated with a Payment entity that has an inheritance strategy of Table-per-Class and a lazy fetch type.

考虑到我们的领域模型,让我们假设我们正在检索一个PaymentReceipt.正如我们已经知道的,它与一个Payment实体相关联,该实体的继承策略是Table-per-Class和一个懒人获取类型

In our case, based on the populated data, the associated Payment of the PaymentReceipt is of type CreditCardPayment. However, since we’re using lazy loading, it would be a proxy object.

在我们的案例中,根据填充的数据,PaymentReceipt的相关PaymentCreditCardPayment.类型。然而,由于我们使用懒惰加载,它将是一个代理对象。

Now, let’s look at the CreditCardPayment entity:

现在,让我们看看CreditCardPayment实体:

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

Indeed, it wouldn’t be possible to retrieve the cardNumber field from the CreditCardPayment class without unproxying the payment object. Regardless, let’s try casting the payment object into a CreditCardPayment and see what will happen:

的确。不可能从ardNumber字段中检索c。spaces=”true”>CreditCardPayment类中的字段,而没有解除对payment对象的代理。无论如何,让我们尝试将payment对象转换为CreditCardPayment,看看会发生什么

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

From the test, we saw the need to cast the payment object into a CreditCardPayment. However, because the payment object is still a Hibernate proxy object, we’ve encountered a ClassCastException.

从测试中,我们看到需要将payment对象投到一个CreditCardPayment。然而,因为数据-reserver-spaces=”true”>的数据-reserver-spaces=”true”>payment数据-reserver-spaces=”true”>对象仍然是一个Hibernate代理对象。我们遇到了一个ClassCastException

5. Hibernate Proxy to Entity Object

5.Hibernate Proxy to Entity Object

Since Hibernate 5.2.10, we can use the built-in static method for unproxying Hibernate entities:

从Hibernate 5.2.10开始,我们可以使用内置的静态方法来解除Hibernate实体的代理。

Hibernate.unproxy(paymentReceipt.getPayment());

Let’s create a final integration test using this approach:

让我们使用这种方法创建一个最终的集成测试:

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

From the test, we can see that we’ve successfully converted a Hibernate proxy to a real entity object.

从测试中,我们可以看到我们已经成功地将一个Hibernate代理转换为一个真正的实体对象。

On the other hand, here’s a solution before Hibernate 5.2.10:

另一方面,这里有一个Hibernate 5.2.10之前的解决方案。

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

6. Conclusion

6.结语

In this tutorial, we’ve learned how to convert a Hibernate proxy to a real entity object. In addition to that, we’ve discussed how the Hibernate proxy works and why it’s useful. Then, we simulated a situation where there’s a need to un proxy an object.

在本教程中,我们已经学会了如何将Hibernate代理转换为一个真正的实体对象。除此之外,我们还讨论了Hibernate代理是如何工作的以及为什么它很有用。然后,我们模拟了一个需要解除代理对象的情况。

Lastly, we ran several integration tests to demonstrate our examples and verify our solution.

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

As always, the full source code of the article is available over on GitHub.

一如既往,该文章的完整源代码可在GitHub上获得