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

Лучший способ сопоставить отношения @OneToOne с JPA и гибернацией

Узнайте, как лучше всего сопоставить связь OneToOne с JPA и гибернацией при использовании как однонаправленных, так и двунаправленных связей.

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