Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье я собираюсь объяснить, как работают прокси-объекты JPA и Hibernate, и как вы можете использовать прокси-сервер сущности для получения доступа к базовому экземпляру POJO.
Механизм отложенной загрузки JPA может быть реализован либо с использованием прокси-серверов, либо с улучшением байт-кода, чтобы можно было перехватывать вызовы отложенных ассоциаций и инициализировать отношения до возврата результата вызывающему.
Изначально, в JPA 1.0, предполагалось, что прокси-серверы не должны быть обязательным требованием, и именно поэтому @Manytoon
и @OneToOne
ассоциации по умолчанию используют стратегию быстрой загрузки. Однако НЕТЕРПЕЛИВАЯ выборка плохо сказывается на производительности поэтому лучше использовать тип FetchType. ЛЕНИВЫЙ
стратегия выборки для всех типов ассоциаций.
В этой статье мы рассмотрим, как работает механизм прокси-сервера и как вы можете отменить прокси-сервер данный прокси-сервер для фактического объекта.
Загрузка прокси-сервера с помощью JPA и переход в спящий режим
JPA EntityManager
определяет два способа загрузки данного объекта.
При вызове метода find
сущность будет загружена либо из кэша первого уровня, либо из кэша второго уровня, либо из базы данных. Следовательно, возвращаемая сущность имеет тот же тип, что и объявленное сопоставление сущностей.
Напротив, при вызове метода getReference
возвращаемый объект является прокси-сервером, а не фактическим типом объекта сущности. Преимущество возврата прокси-сервера заключается в том, что мы можем инициализировать родителя @manytoon
или @OneToOne
ассоциация без необходимости обращаться к базе данных, когда мы просто хотим установить столбец внешнего ключа со значением, которое мы уже знаем.
Итак, при выполнении следующего примера:
Post post = entityManager.getReference(Post.class, 1L); PostComment comment = new PostComment(); comment.setId(1L); comment.setPost(post); comment.setReview("A must read!"); entityManager.persist(comment);
Hibernate выпустит одну инструкцию INSERT без необходимости выполнения какой-либо инструкции SELECT:
INSERT INTO post_comment (post_id, review, id) VALUES (1, 'A must read!', 1)
Хотя в этом примере подчеркивается, когда прокси полезны для записи данных, прокси также очень удобны для чтения данных.
Учитывая, что у нас есть следующий Комментарий к сообщению
сущность:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment { @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String review; //Getters and setters omitted for brevity }
При выполнении следующего тестового случая:
PostComment comment = entityManager.find( PostComment.class, 1L ); LOGGER.info("Loading the Post Proxy"); assertEquals( "High-Performance Java Persistence", comment.getPost().getTitle() );
Hibernate генерирует следующие выходные данные:
SELECT pc.id AS id1_1_0_, pc.post_id AS post_id3_1_0_, pc.review AS review2_1_0_ FROM post_comment pc WHERE pc.id = 1 -- Loading the Post Proxy SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1
Первая инструкция SELECT извлекает Комментарий к сообщению
сущность без инициализации родителя Сообщение
ассоциация, так как оно было помечено Типом выборки. ЛЕНИВЫЙ
. Проверяя выбранный столбец ВНЕШНЕГО КЛЮЧА, Hibernate знает, следует ли установить post
ассоциацию в null
или в прокси-сервер. Если значение столбца ВНЕШНЕГО КЛЮЧА не равно нулю, то прокси-сервер заполнит только идентификатор ассоциации.
Однако при доступе к атрибуту title
Hibernate необходимо выполнить дополнительную инструкцию SELECT для инициализации Post
Прокси.
Как прокси-сервер прокси-объекта с помощью JPA и гибернации
Как мы уже видели, при навигации по прокси-объекту Hibernate выдает инструкцию secondary SELECT и инициализирует ассоциацию. Следовательно, прокси-сервер заменяется фактическим объектом сущности.
Учитывая, что объект Post
отображается следующим образом:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id private Long id; private String title; //Getters and setters omitted for brevity @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Post)) return false; return id != null && id.equals(((Post) o).getId()); } @Override public int hashCode() { return getClass().hashCode(); } }
При выполнении следующего тестового случая:
Post _post = doInJPA(entityManager -> { Post post = new Post(); post.setId(1L); post.setTitle("High-Performance Java Persistence"); entityManager.persist(post); return post; }); doInJPA(entityManager -> { Post post = entityManager.getReference(Post.class, 1L); LOGGER.info( "Post entity class: {}", post.getClass().getName() ); assertFalse(_post.equals(post)); assertTrue( _post.equals(Hibernate.unproxy(post)) ); });
Hibernate генерирует следующие выходные данные:
Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateProxyTest$Post_$$_jvst8fd_0
Поскольку класс объектов Прокси является динамически генерируемым типом, поэтому объект Прокси post
не равен объекту _post
, который является фактическим Сообщение
экземпляр класса.
Однако после вызова метода proxy
, введенного в Hibernate 5.2.10 , исходная _post
сущность и непрофилированный post
объект равны.
До перехода в режим гибернации 5.2.10, чтобы отменить блокировку объекта без его обхода, вам придется выполнить следующую логику:
Object unproxiedEntity = null; if(proxy instanceof HibernateProxy) { HibernateProxy hibernateProxy = (HibernateProxy) proxy; LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); unproxiedEntity = initializer.getImplementation(); }
Не очень приятно, правда? К счастью, начиная с Hibernate ORM 5.2.10, вы можете отключить прокси-сервер Hibernate с помощью утилиты Hibernate#proxy
:
Object unproxiedEntity = Hibernate.unproxy(proxy);
Намного лучше!
Вывод
Понимание внутренних функций гибернации может сделать разницу между приложением, которое едва ползает, и приложением, которое работает со скоростью деформации. Ленивые ассоциации очень важны с точки зрения производительности, и вам действительно нужно понимать, как работают прокси, поскольку вы неизбежно будете сталкиваться с ними ежедневно.