Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Один к одному

Некоторые данные принадлежат ровно одному другому объекту: у каждого автора один профиль с биографией, и каждый профиль относится к одному автору. Это связь один к одному. В базе это снова внешний ключ, но уникальный: два профиля не могут ссылаться на одного автора.

Владеющая сторона

Внешний ключ размещают в той таблице, которая «зависит» от другой, — здесь в профиле:

// src/main/java/com/example/blog/Profile.java
package com.example.blog;
 
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
 
@Entity
public class Profile {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String bio;
 
    @OneToOne
    @JoinColumn(name = "author_id", nullable = false)
    private Author author;
 
    protected Profile() {
    }
 
    public Profile(Author author, String bio) {
        this.author = author;
        this.bio = bio;
        author.setProfile(this);
    }
 
    public String getBio() {
        return bio;
    }
 
    public Author getAuthor() {
        return author;
    }
}

Другая сторона

// src/main/java/com/example/blog/Author.java
@Entity
public class Author {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    @OneToOne(mappedBy = "author")
    private Profile profile;
 
    // конструкторы, getName(), getProfile()
 
    void setProfile(Profile profile) {
        this.profile = profile;
    }
}

Как и у @OneToMany, mappedBy указывает на поле владеющей стороны. Конструктор Profile заполняет обе стороны сразу.

Использование

// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
    Author anna = new Author("Анна");
    Profile profile = new Profile(anna, "Пишет о Java и базах данных");
    em.persist(anna);
    em.persist(profile);
});
 
emf.runInTransaction(em -> {
    Author author = em.find(Author.class, 1L);
    IO.println(author.getName() + ": " + author.getProfile().getBio());
});
Hibernate: create table Author (id bigint not null, name varchar(255), primary key (id))
Hibernate: create table Profile (author_id bigint not null unique, id bigint not null, bio varchar(255), primary key (id))
Hibernate: alter table if exists Profile add constraint FK4tbt6ixibsme3rpfs7n6iagoa foreign key (author_id) references Author
Hibernate: insert into Author (name,id) values (?,?)
Hibernate: insert into Profile (author_id,bio,id) values (?,?,?)
Hibernate: select a1_0.id,a1_0.name,p1_0.id,p1_0.author_id,p1_0.bio from Author a1_0 left join Profile p1_0 on a1_0.id=p1_0.author_id where a1_0.id=?
Анна: Пишет о Java и базах данных

Hibernate сам добавил unique к author_id. При загрузке автора он присоединяет профиль в том же запросе: ему нужно знать, существует ли профиль.

Общий первичный ключ

Профиль может использовать id автора как собственный id. Тогда лишний столбец пропадает:

// src/main/java/com/example/blog/Profile.java
@Id
private Long id;
 
@OneToOne
@MapsId
private Author author;
Hibernate: create table Profile (author_id bigint not null, bio varchar(255), primary key (author_id))
Hibernate: insert into Profile (bio,author_id) values (?,?)

@MapsId означает «взять id из связи author». Профиль с id 1 всегда принадлежит автору с id 1, а @GeneratedValue не нужен. Остальной код не меняется.

Частая ошибка

Уникальный ключ запрещает второй профиль для того же автора. Попытка его сохранить падает при фиксации:

Exception in thread "main" jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [Unique index or primary key violation: "PUBLIC.CONSTRAINT_1 INDEX PUBLIC.CONSTRAINT_INDEX_1 ON PUBLIC.PROFILE(AUTHOR_ID NULLS FIRST) VALUES ( /* 1 */ CAST(1 AS BIGINT) )"; ...

Чтобы заменить профиль, меняйте поля существующего, а не создавайте новый объект.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.