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 .