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.为了更好地理解这一场景,我们来看看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;
...
}
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 CreditCardPayment — it’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:
我们在前面已经了解了一些关于这一点。为了赋予它更多的意义,让我们尝试从PaymentReceipt和Payment实体中移除懒惰加载机制。
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的相关Payment是CreditCardPayment.类型。然而,由于我们使用懒惰加载,它将是一个代理对象。
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上获得。