Encodera

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

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

Оглавление

Многие к одному и один ко многим

У статьи много комментариев, а каждый комментарий относится к одной статье. В базе это столбец post_id в таблице комментариев, который ссылается на статью, — внешний ключ. В Java — поле типа Post в классе Comment и, если нужно, список комментариев в Post. Hibernate связывает эти два мира.

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

// src/main/java/com/example/blog/Comment.java
package com.example.blog;
 
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
 
@Entity
public class Comment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String text;
 
    @ManyToOne
    @JoinColumn(name = "post_id", nullable = false)
    private Post post;
 
    protected Comment() {
    }
 
    public Comment(String text) {
        this.text = text;
    }
 
    public String getText() {
        return text;
    }
 
    public Post getPost() {
        return post;
    }
 
    void setPost(Post post) {
        this.post = post;
    }
}

@ManyToOne означает «много комментариев к одной статье». @JoinColumn задаёт имя столбца внешнего ключа. Сторону с внешним ключом называют владеющей: то, что записано в её поле, и попадает в базу.

Обратная сторона: @OneToMany

// src/main/java/com/example/blog/Post.java
@Entity
public class Post {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();
 
    // конструкторы
 
    public void addComment(Comment comment) {
        comments.add(comment);
        comment.setPost(this);
    }
 
    public List<Comment> getComments() {
        return comments;
    }
}

mappedBy = "post" говорит Hibernate: «этот список описан полем post в Comment, ничего отдельного для него не создавай». Метод addComment обновляет обе стороны сразу, чтобы объекты в памяти всегда были согласованы. Зарегистрируйте Comment в persistence.xml.

Сохранение и чтение

// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
    Post post = new Post("Связи");
    Comment first = new Comment("Отличная статья!");
    Comment second = new Comment("Спасибо, Анна");
    post.addComment(first);
    post.addComment(second);
 
    em.persist(post);
    em.persist(first);
    em.persist(second);
});
 
emf.runInTransaction(em -> {
    Post post = em.find(Post.class, 1L);
    IO.println(post.getTitle() + ", комментариев: " + post.getComments().size());
    for (Comment comment : post.getComments()) {
        IO.println("- " + comment.getText());
    }
});
 
emf.runInTransaction(em -> {
    Comment comment = em.find(Comment.class, 1L);
    IO.println("Комментарий к: " + comment.getPost().getTitle());
});

Вывод:

Hibernate: create table Comment (id bigint not null, post_id bigint not null, text varchar(255), primary key (id))
Hibernate: create table Post (id bigint not null, title varchar(255), primary key (id))
Hibernate: alter table if exists Comment add constraint FKqb0rnht649ifuh6gev5lwvx8x foreign key (post_id) references Post
Hibernate: insert into Post (title,id) values (?,?)
Hibernate: insert into Comment (post_id,text,id) values (?,?,?)
Hibernate: insert into Comment (post_id,text,id) values (?,?,?)
Hibernate: select p1_0.id,p1_0.title from Post p1_0 where p1_0.id=?
Hibernate: select c1_0.post_id,c1_0.id,c1_0.text from Comment c1_0 where c1_0.post_id=?
Связи, комментариев: 2
- Отличная статья!
- Спасибо, Анна
Hibernate: select c1_0.id,c1_0.post_id,p1_0.id,p1_0.title,c1_0.text from Comment c1_0 join Post p1_0 on p1_0.id=c1_0.post_id where c1_0.id=?
Комментарий к: Связи

Обратите внимание: комментарии загрузились отдельным запросом только тогда, когда понадобился список, а статья комментария пришла в том же запросе через join. Почему так, объясняет урок о ленивой загрузке. Каждый объект пришлось передать в persist отдельно; как сохранить статью сразу с комментариями, показывает урок о каскадах.

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

Без mappedBy Hibernate считает список отдельной, независимой связью и создаёт лишнюю промежуточную таблицу:

Hibernate: create table Post_Comment (Post_id bigint not null, comments_id bigint not null unique)
Hibernate: insert into Post_Comment (Post_id,comments_id) values (?,?)

Каждый комментарий теперь хранится дважды: в post_id и в Post_Comment. В двусторонней связи всегда указывайте в mappedBy поле владеющей стороны.

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

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