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

Впадать В Спячку Лениво До Одной аннотации

Узнайте, как работает аннотация Hibernate Lazy To One и почему вы должны использовать ленивую загрузку NO_PROXY с улучшением байт-кода.

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

Вступление

В этой статье я собираюсь объяснить, как работает аннотация Hibernate Lazy To One и почему вы должны использовать NO_PROXY ленивую загрузку с улучшением байт-кода.

Без аннотации LazyToOneOption.NO_PROXY родительская сторона ассоциации @OneToOne всегда будет извлекаться с нетерпением, даже если вы установите для нее значение FetchType.ЛЕНИВЫЙ и включена функция ленивой загрузки для улучшения байт-кода.

Аннотацию Hibernate Lazy To One и перечисление LazyToOneOption

Аннотация Hibernate @LazyToOne выглядит следующим образом:

Атрибут значение принимает перечисление LazyToOneOption , которое предоставляет одно из следующих трех значений:

public enum LazyToOneOption {
    FALSE,
    PROXY,
    NO_PROXY
}

Далее мы посмотрим, как все эти три варианта работают с JPA и Hibernate.

Ленивый вариант.Аннотация ложного режима гибернации

Если вы используете опцию LazyToOneOption.FALSE , ассоциация будет извлечена с нетерпением, даже если она использует тип FetchType.ЛЕНИВЫЙ стратегия извлечения.

Итак, учитывая, что у нас есть следующий родительский Пост объект:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;
    
    //Getters and setters omitted for brevity
}

И следующий клиент Сведения о публикации сущность, которая определяет взаимно однозначную связь с использованием @MapsId для совместного использования идентификатора со своей родительской сущностью публикации :

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "created_on")
    private Date createdOn = new Date();

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @LazyToOne(LazyToOneOption.FALSE)
    @MapsId
    @JoinColumn(name = "id")
    private Post post;
    
    //Getters and setters omitted for brevity
}

Обратите внимание, что для атрибута fetch аннотации @OneToOne установлено значение FetchType.ЛЕНИВЫЙ .

Однако у нас также есть @LazyToOne(опция LazyToOneOption.FALSE) аннотация, установленная в сообщении ассоциации.

Если мы добавим следующие Сообщения и Сведения о сообщениях сущности:

final Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence, 1st Part");

doInJPA(entityManager -> {
    entityManager.persist(post);

    entityManager.persist(
        new PostDetails()
            .setPost(post)
            .setCreatedBy("Vlad Mihalcea")
    );
});

Если мы хотим получить Сведения о публикации сущности:

PostDetails details = doInJPA(entityManager -> {
    return entityManager.find(PostDetails.class, post.getId());
});

assertNotNull(details.getPost());

Мы ожидали бы, что ассоциация post будет представлена неинициализированным прокси-сервером, но этого не произойдет. Вместо этого Hibernate выполняет 2 SQL-запроса:

SELECT pd.id AS id1_1_0_,
       pd.created_by AS created_2_1_0_,
       pd.created_on AS created_3_1_0_
FROM post_details pd
WHERE pd.id = 1

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM post p
WHERE p.id = 1

Первый SQL-запрос-это тот, который мы ожидали. Второй, который охотно извлекает сущность Post , был выполнен, потому что мы аннотировали ассоциацию post с @LazyToOne(LazyToOneOption.ЛОЖЬ) аннотация.

За кулисами именно так интерпретируется аннотация @LazyToOne в Hibernate:

LazyToOne lazy = property.getAnnotation(LazyToOne.class);

if ( lazy != null ) {
    toOne.setLazy( 
        !(lazy.value() == LazyToOneOption.FALSE) 
    );
    
    toOne.setUnwrapProxy(
        (lazy.value() == LazyToOneOption.NO_PROXY)
    );
}

@LazyToOne(опция lazytoone.FALSE) Аннотация Hibernate работает так, как если бы вы установили для стратегии fetch значение FetchType.НЕТЕРПЕЛИВЫЙ .

Ленивый вариант.Аннотация гибернации ПРОКСИ-сервера

Если мы переключим значение LazyToOneOption с FALSE на ПРОКСИ , как показано в следующем примере:

@OneToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.PROXY)
@MapsId
@JoinColumn(name = "id")
private Post post;

И мы получаем Сведения о публикации сущность:

PostDetails details = doInJPA(entityManager -> {
    return entityManager.find(PostDetails.class, post.getId());
});

assertNotNull(details.getPost());
LOGGER.info("Post entity class: {}", details.getPost().getClass());

Мы видим, что на этот раз выполняется один SQL-запрос:

SELECT pd.id AS id1_1_0_,
       pd.created_by AS created_2_1_0_,
       pd.created_on AS created_3_1_0_
FROM post_details pd
WHERE pd.id = 1

И класс ссылки на объект Post – это Прокси-сервер гибернации :

-- Post entity class: Post$HibernateProxy$QrlX9iOq

Это поведение по умолчанию для FetchType.ЛЕНИВЫЕ ассоциации, поэтому мы получаем тот же результат, даже если опустим @LazyToOne(LazyToOneOption.ПРОКСИ) аннотация.

@LazyToOne(опция lazytoone.ПРОКСИ) Аннотация Hibernate является избыточной, если в ассоциации используется тип FetchType.ЛЕНИВЫЙ стратегия.

Аннотация LazyToOneOption.NO_PROXY в режиме гибернации

Чтобы понять, где полезна аннотация LazyToOneOption.NO_PROXY , давайте изменим предыдущую ассоциацию @OneToOne с однонаправленной на двунаправленную. Таким образом, в то время как Сведения о публикации сопоставление остается прежним, родительская Запись сущность также будет содержать сведения свойство:

Итак, отображение Post сущности выглядит следующим образом:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;
    
    @OneToOne(
        mappedBy = "post",
        fetch = FetchType.LAZY,
        cascade = CascadeType.ALL
    )
    private PostDetails details;
    
    //Getters and setters omitted for brevity
}

Как объяснено в этой статье , родительская сторона ассоциации @OneToOne всегда извлекается с нетерпением, даже если для нее установлено значение FetchType.ЛЕНИВЫЙ .

Итак, при извлечении объекта Post :

Post post = doInJPA(entityManager -> {
    return entityManager.find(Post.class, 1L);
});

Hibernate собирается выполнить два SQL-запроса вместо одного:

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM post p
WHERE p.id = 1

SELECT pd.id AS id1_1_0_,
       pd.created_by AS created_2_1_0_,
       pd.created_on AS created_3_1_0_
FROM post_details pd
WHERE pd.id = 1

И при проверке сущности post мы видим, что ассоциация details извлекается, даже если мы установили для нее значение FetchType.ЛЕНИВЫЙ :

post = {Post@5438} 
  id = {Long@5447} 1
  title = "High-Performance Java Persistence, 1st Part"
  details = {PostDetails@5449} 
    id = {Long@5447} 1
    createdOn = {Timestamp@5452} "2021-01-06 15:35:18.708"
    createdBy = "Vlad Mihalcea"
    post = {Post@5438}

Это нежелательно, поскольку, если мы выберем N Post сущностей без необходимости извлекать связанные с ними сведения ассоциации, Hibernate выполнит N дополнительных SQL-запросов, что приведет к N+1 проблеме с запросом .

Таким образом, чтобы избежать этой проблемы, нам нужно включить ленивую загрузку для улучшения байт-кода:


    org.hibernate.orm.tooling
    hibernate-enhance-maven-plugin
    ${hibernate.version}
    
        
            
                true
            
            
                enhance
            
        
    

Однако этого недостаточно. Нам также нужно аннотировать свойство details с помощью @LazyToOne(LazyToOneOption.NO_PROXY) :

@OneToOne(
    mappedBy = "post",
    fetch = FetchType.LAZY,
    cascade = CascadeType.ALL
)
@LazyToOne(LazyToOneOption.NO_PROXY)
private PostDetails details;

Теперь, при извлечении родительской Записи сущности, мы видим, что генерируется один SQL-запрос:

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM post p
WHERE p.id = 1

И сущность Post извлекается следующим образом:

post = {Post@5475} 
  id = {Long@5484} 1
  title = "High-Performance Java Persistence, 1st Part"
  details = null
  $$_hibernate_entityEntryHolder = null
  $$_hibernate_previousManagedEntity = null
  $$_hibernate_nextManagedEntity = null
  $$_hibernate_attributeInterceptor = {LazyAttributeLoadingInterceptor@5486}

Свойства $$_hibernate_ вводятся механизмом улучшения байт-кода, а перехватчик $$_hibernate_attribute отвечает за перехват вызовов метода getter и инициализацию сведений прокси по требованию.

Без аннотации @LazyToOne(LazyToOneOption.NO_PROXY) ассоциация подробности будет получена с нетерпением, даже если включен механизм ленивой загрузки для улучшения байт-кода.

Вывод

Аннотация LazyToOne Hibernate позволяет нам управлять стратегией выборки, выходящей за рамки параметров FetchType по умолчанию. Хотя опции FALSE и ПРОКСИ редко требуются, опция NO_PROXY очень полезна при использовании двунаправленных @OneToOne ассоциаций.

Без использования аннотации Hibernate @LazyToOne(LazyToOneOption.NO_PROXY) родительская сторона двунаправленной @OneToOne ассоциации будет использовать Тип выборки.НЕТЕРПЕЛИВАЯ стратегия, даже если мы явно пометили ее FetchType.ЛЕНИВЫЙ и включил ленивую загрузку для улучшения байт-кода.