Автор оригинала: Vlad Mihalcea.
В этой статье мы узнаем, как лучше всего сопоставить связь OneToOne с JPA и Hibernate.
Хотя существует множество способов сопоставления отношений “один к одному” с Hibernate, я собираюсь продемонстрировать, какое сопоставление является наиболее эффективным с точки зрения базы данных.
Отличная статья ! Лучший способ сопоставить отношения OneToOne с JPA и гибернацией https://t.co/p7TPsGoUxi через @vlad_mihalcea
Для следующих примеров я собираюсь использовать следующие Сообщения
и Сведения о сообщениях
классы:
Сущность Post
является родительской, в то время как Сведения о публикации
является дочерней ассоциацией, поскольку Внешний ключ находится в таблице базы данных post_details
.
Чаще всего эта взаимосвязь отображается следующим образом:
@Entity(name = "PostDetails") @Table(name = "post_details") public class PostDetails { @Id @GeneratedValue private Long id; @Column(name = "created_on") private Date createdOn; @Column(name = "created_by") private String createdBy; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") private Post post; public PostDetails() {} public PostDetails(String createdBy) { createdOn = new Date(); this.createdBy = createdBy; } //Getters and setters omitted for brevity }
Более того, даже объект Post
также может иметь отображение Сведений о записи
:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id @GeneratedValue private Long id; private String title; @OneToOne(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false) private PostDetails details; //Getters and setters omitted for brevity public void setDetails(PostDetails details) { if (details == null) { if (this.details != null) { this.details.setPost(null); } } else { details.setPost(this); } this.details = details; } }
Однако, как было показано далее, это сопоставление не является наиболее эффективным.
Таблица post_details
содержит столбец первичного ключа (PK) (например, id
) и столбец внешнего ключа (FK) (например, post_id
).
Однако может быть только одна строка post_details
, связанная с сообщением
, поэтому имеет смысл, чтобы post_details
PK отображал сообщение
PK.
Таким образом, Первичный ключ post_details
также является внешним ключом, и две таблицы также совместно используют свои компьютеры.
Столбцы PK и FK чаще всего индексируются, поэтому совместное использование PK может уменьшить объем индекса вдвое, что желательно, поскольку вы хотите сохранить все свои индексы в памяти для ускорения сканирования индексов.
В то время как однонаправленная @OneToOne
ассоциация может быть извлечена лениво, родительская сторона двунаправленной @OneToOne
ассоциации-нет. Даже если указать, что связь не является необязательной
и у нас есть Тип выборки.ЛЕНИВЫЙ
, родительская ассоциация ведет себя как Тип выборки.НЕТЕРПЕЛИВЫЙ
отношения. И НЕТЕРПЕЛИВАЯ выборка-это плохо .
Это можно легко продемонстрировать, просто выбрав объект Post
:
Post post = entityManager.find(Post.class, 1L);
Hibernate также извлекает дочернюю сущность, поэтому вместо одного запроса Hibernate требует двух операторов select:
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1 SELECT pd.post_id AS post_id3_1_0_, pd.created_by AS created_1_1_0_, pd.created_on AS created_2_1_0_ FROM post_details pd WHERE pd.post_id = 1
Даже если FK НЕ РАВЕН НУЛЮ
, и родительская сторона знает о его ненулевой возможности с помощью необязательного
атрибута (например, @OneToOne(сопоставлено,.ЛЕНИВЫЙ,)
), Hibernate по-прежнему генерирует дополнительный оператор select.
Для каждой управляемой сущности Контекст сохранения требует как типа сущности, так и идентификатора, поэтому дочерний идентификатор должен быть известен при загрузке родительской сущности, и единственный способ найти связанный post_details
первичный ключ-это выполнить вторичный запрос.
Улучшение байт-кода является единственным жизнеспособным обходным путем. Однако это работает только в том случае, если родительская сторона помечена @LazyToOne(LazyToOneOption.NO_PROXY)
.
Для получения более подробной информации об этой теме ознакомьтесь с этой статьей .
Лучший способ отобразить отношения @OneToOne
– использовать @MapsId
. Таким образом, вам даже не нужна двунаправленная ассоциация, так как вы всегда можете получить сведения о записи
сущности, используя идентификатор записи
сущности.
Отображение выглядит следующим образом:
@Entity(name = "PostDetails") @Table(name = "post_details") public class PostDetails { @Id private Long id; @Column(name = "created_on") private Date createdOn; @Column(name = "created_by") private String createdBy; @OneToOne(fetch = FetchType.LAZY) @MapsId private Post post; public PostDetails() {} public PostDetails(String createdBy) { createdOn = new Date(); this.createdBy = createdBy; } //Getters and setters omitted for brevity }
Таким образом, свойство id
служит как Первичным ключом, так и Внешним ключом. Вы заметите, что столбец @Id
больше не использует аннотацию @GeneratedValue
, поскольку идентификатор заполняется идентификатором ассоциации post
.
Если вы хотите настроить имя столбца первичного ключа при использовании @MapsId
, вам необходимо использовать аннотацию @JoinColumn
. Для получения более подробной информации ознакомьтесь с этой статьей .
Сведения о публикации
сущность могут быть сохранены следующим образом:
doInJPA(entityManager -> { Post post = entityManager.find(Post.class, 1L); PostDetails details = new PostDetails("John Doe"); details.setPost(post); entityManager.persist(details); });
И мы даже можем получить данные Post
, используя идентификатор Post
сущности, поэтому нет необходимости в двунаправленной ассоциации:
PostDetails details = entityManager.find( PostDetails.class, post.getId() );
Знание того, как эффективно сопоставлять отношения сущностей, может существенно повлиять на производительность приложений. При использовании JPA и Hibernate связь “Один к одному” всегда должна совместно использовать первичный ключ с родительской таблицей.
И, если вы не используете усовершенствование байт-кода, вам следует избегать двунаправленной связи.
Код доступен на GitHub .