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

Как работает прокси-сервер JPA и как отключить его с помощью Hibernate

Узнайте, как работают прокси-объекты JPA и Hibernate и как можно использовать прокси-сервер сущности для получения доступа к базовому экземпляру POJO.

Автор оригинала: 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);

Намного лучше!

Вывод

Понимание внутренних функций гибернации может сделать разницу между приложением, которое едва ползает, и приложением, которое работает со скоростью деформации. Ленивые ассоциации очень важны с точки зрения производительности, и вам действительно нужно понимать, как работают прокси, поскольку вы неизбежно будете сталкиваться с ними ежедневно.