Рубрики
Без рубрики

Как преобразовать прокси-сервер Hibernate в реальный объект сущности

Автор оригинала: baeldung.

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 .