Автор оригинала: 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.ЛЕНИВЫЙ
и включил ленивую загрузку для улучшения байт-кода.