Many-to-one and one-to-many
A post has many comments, and each comment belongs to one post. In the database this is a post_id column in the comment table that refers to the post — a foreign key. In Java it is a field of type Post in Comment and, if needed, a list of comments in Post. Hibernate connects the two worlds.
The owning side: @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 says "many comments to one post". @JoinColumn names the foreign key column. The side with the foreign key is called the owning side: what is written in its field ends up in the database.
The reverse side: @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<>();
// constructors
public void addComment(Comment comment) {
comments.add(comment);
comment.setPost(this);
}
public List<Comment> getComments() {
return comments;
}
}mappedBy = "post" tells Hibernate: "this list is described by the post field in Comment, don't create anything for it". The addComment method updates both sides at once, so the objects in memory always agree with each other. Register Comment in persistence.xml.
Saving and reading
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
Post post = new Post("Relationships");
Comment first = new Comment("Great post!");
Comment second = new Comment("Thanks, Anna");
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() + ", comments: " + 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 to: " + comment.getPost().getTitle());
});Output:
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=?
Relationships, comments: 2
- Great post!
- Thanks, Anna
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=?
Comment to: RelationshipsNote that the comments were loaded by a separate query only when the list was used, while the post of a comment came in the same query with a join. Why it works this way is explained in the lesson on lazy loading. Each object had to be passed to persist separately; the lesson on cascading shows how to save a post together with its comments.
Common mistake
Without mappedBy Hibernate treats the list as a separate, independent relationship and creates an extra join table:
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 (?,?)Every comment is now stored twice: in post_id and in Post_Comment. In a bidirectional relationship always point mappedBy at the field of the owning side.