1. Обзор
В этом уроке мы узнаем, как преобразовать прокси-сервер Hibernate в реальный объект сущности. До этого мы поймем, когда Hibernate создает прокси-объект. Затем мы поговорим о том, почему прокси-сервер Hibernate полезен. И, наконец, мы смоделируем сценарий, в котором необходимо отменить прокси-сервер объекта.
2. Когда Hibernate Создает прокси-объект?
Hibernate использует прокси-объекты, чтобы разрешить ленивую загрузку . Чтобы лучше визуализировать сценарий, давайте посмотрим на Платежную квитанцию и Платеж сущности:
@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.ЛЕНИВЫЙ .
Чтобы продемонстрировать это, давайте создадим и запустим интеграционный тест:
@Test
public void givenPaymentReceipt_whenAccessingPayment_thenVerifyType() {
PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L);
Assert.assertTrue(paymentReceipt.getPayment() instanceof HibernateProxy);
}Из теста мы загрузили PaymentReceipt и проверили, что объект payment не является экземпляром CreditCardPayment — это HibernateProxy объект .
В отличие от этого, без ленивой загрузки предыдущий тест провалился бы, поскольку возвращенный объект payment был бы экземпляром CreditCardPayment .
Кроме того, стоит упомянуть, что Hibernate использует байт-код приборы чтобы создать прокси-объект.
Чтобы проверить это, мы можем добавить точку останова в строку утверждения интеграционного теста и запустить его в режиме отладки. Теперь давайте посмотрим, что показывает отладчик:
paymentReceipt = {[email protected]}
payment = {[email protected]} "[email protected]"
$$_hibernate_interceptor = {[email protected]} Из отладчика мы видим, что Hibernate использует Byte Buddy , который является библиотекой для динамического создания классов Java во время выполнения.
3. Почему Прокси-Сервер Hibernate Полезен?
3.1. Прокси-сервер гибернации для ленивой загрузки
Мы немного узнали об этом раньше. Чтобы придать ему большее значение, давайте попробуем удалить механизм ленивой загрузки из Квитанции об оплате и Оплаты сущностей:
public class PaymentReceipt {
...
@OneToOne
private Payment payment;
...
}public abstract class Payment {
...
@ManyToOne
protected WebUser webUser;
...
}Теперь давайте быстро получим Квитанцию об оплате и проверим сгенерированный 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=?Как мы можем видеть из журналов, запрос на Квитанция об оплате содержит несколько операторов соединения.
Теперь давайте запустим его с ленивой загрузкой на месте:
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=?Очевидно, что сгенерированный SQL упрощается, опуская все ненужные операторы join.
3.2. Прокси-сервер гибернации для записи данных
Чтобы проиллюстрировать это, давайте используем его для создания Платежа и назначения ему Веб-пользователя . Без использования прокси-сервера это приведет к двум операторам SQL: SELECT оператор для получения Веб-пользователя и 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 выполнил только один оператор: INSERT оператор для Оплаты | создания.
4. Сценарий: Необходимость проксирования
Учитывая нашу модель домена, предположим, что мы получаем квитанцию об оплате|/. Как мы уже знаем, он связан с сущностью Payment , которая имеет стратегию наследования | Table-per-Class и тип ленивой выборки .
В нашем случае, основываясь на заполненных данных, связанный Платеж Платежной квитанции имеет тип Оплата кредитной картой. Однако, поскольку мы используем ленивую загрузку, это будет прокси-объект.
Теперь давайте рассмотрим Платеж по кредитной карте юридическое лицо:
@Entity
public class CreditCardPayment extends Payment {
private String cardNumber;
...
}В самом деле, было бы невозможно получить поле c ard Number из класса Оплата кредитной картой без удаления объекта оплата . Независимо от этого, давайте попробуем преобразовать объект payment в Платеж по кредитной карте и посмотрим, что произойдет :
@Test
public void givenPaymentReceipt_whenCastingPaymentToConcreteClass_thenThrowClassCastException() {
PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L);
assertThrows(ClassCastException.class, () -> {
CreditCardPayment creditCardPayment = (CreditCardPayment) paymentReceipt.getPayment();
});
}Из теста мы увидели необходимость превратить объект payment в Платеж по кредитной карте . Однако, поскольку объект payment по – прежнему является прокси-объектом Hibernate, мы столкнулись с ClassCastException .
5. Прокси-сервер гибернации для объекта сущности
Начиная с 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 в реальный объект сущности.
С другой стороны, вот решение перед гибернацией 5.2.10:
HibernateProxy hibernateProxy = (HibernateProxy) paymentReceipt.getPayment(); LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); CreditCardPayment unproxiedEntity = (CreditCardPayment) initializer.getImplementation();
6. Заключение
В этом уроке мы узнали, как преобразовать прокси-сервер Hibernate в реальный объект сущности. В дополнение к этому мы обсудили, как работает прокси-сервер Hibernate и почему он полезен. Затем мы смоделировали ситуацию, когда возникает необходимость распаковать объект.
Наконец, мы провели несколько интеграционных тестов, чтобы продемонстрировать наши примеры и проверить наше решение.
Как всегда, полный исходный код статьи доступен на GitHub .