Spring Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> LEARN SPRING
Persistence top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

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.

2. When Does Hibernate Create a Proxy Object?

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

@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.

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.

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

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

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 = {[email protected]} 
 payment = {[email protected]} "[email protected]"
  $$_hibernate_interceptor = {[email protected]} 

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

3. Why Is Hibernate Proxy Useful?

3.1. Hibernate Proxy for Lazy Loading

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:

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:

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. 

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.

3.2. Hibernate Proxy for Writing Data

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.

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.

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.

4. Scenario: The Need for Unproxying

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.

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.

Now, let's look at the CreditCardPayment entity:

@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:

@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.

5. Hibernate Proxy to Entity Object

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

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.

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

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

6. Conclusion

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.

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.

Spring bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> THE COURSE
Persistence bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Comments are closed on this article!