Многие к одному и один ко многим
У статьи много комментариев, а каждый комментарий относится к одной статье. В базе это столбец 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 поле владеющей стороны.