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 unproxy 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 CreditCardPayment — it'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 = {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.
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 unproxy 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.
The post How to Convert a Hibernate Proxy to a Real Entity Object first appeared on Baeldung.