Spring Boot JPA One to Many

Lets learn how to use Spring Boot JPA to implement One to Many relationship.

you can model Many-to-One and One-to-Many relationships between entities using the @ManyToOne and @OneToMany annotations.

Bidirectional

We can have two entities Post and Comment. A post can have multiple comments and a comment belongs to a post.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;

@ManyToOne
@JoinColumn(name = "post_id")
private Post post;

// getters and setters
}

The @ManyToOne annotation is used to create a many-to-one relationship between the Comment and Post entities. The @JoinColumn annotation is used to specify the foreign key column in the Comment entity that refers to the primary key column in the Post entity.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "post")
private List<Comment> comments;

// getters and setters
}

A post can have multiple comments. The mappedBy attribute is used to specify the property in the Comment entity that owns the relationship.

Unidirectional with @ManyToOne

Bidirectional One-to-Many relationship is the most common way to map a one-to-many relationship. It is possible to create a unidirectional One-to-Many/Many-To-One relationship as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;

@ManyToOne
@JoinColumn(name = "post_id")
private Post post;

// getters and setters
}

@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;

// getters and setters
}

@ManyToOne annotation is used to create a many-to-one relationship between the Comment and Post entities. The @JoinColumn annotation is used to specify the foreign key column in the Comment entity that refers to the primary key column in the Post entity.

Post doesn’t have a reference to Comment.

Use case: If you ONLY need to navigate from the owning entity to the owned entity, you can use a unidirectional relationship.

Cascade Types

The cascade attribute is used to specify the operations that should be cascaded to the target of the association. The cascade attribute can be specified on the @OneToOne, @OneToMany, and @ManyToMany annotations.

Optionas are:

  • CascadeType.ALL: All operations are cascaded.
  • CascadeType.PERSIST: Only persist operation is cascaded.
  • CascadeType.MERGE: Only merge operation is cascaded.
  • CascadeType.REMOVE: Only remove operation is cascaded.
  • CascadeType.REFRESH: Only refresh operation is cascaded.
  • CascadeType.DETACH: Only detach operation is cascaded.
1
2
@OneToMany(cascade = CascadeType.ALL, mappedBy = "post")
private List<Comment> comments;

Here we are using CascadeType.ALL to cascade all operations to Comment entity. If we save a Post entity, all associated Comment entities will be saved automatically. If we delete a Post entity, all associated Comment entities will be deleted automatically.

1
2
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "post")
private List<Comment> comments;

Here we are using CascadeType.PERSIST to cascade only persist operation to Comment entity. If we save a Post entity, all associated Comment entities will be saved automatically. If we delete a Post entity, associated Comment entities will not be deleted automatically.

It is common to use CascadeType.ALL or CascadeType.PERSIST when you have a bidirectional relationship. Otherwise, you may end up with orphaned entities in the database.

FetchType

The fetch attribute is used to specify the fetch type for the association. The fetch attribute can be specified on the @OneToOne, @OneToMany, and @ManyToMany annotations. Default fetch type is FetchType.LAZY.

Options are:

  • FetchType.EAGER: The associated entity is loaded eagerly when the owning entity is loaded.
  • FetchType.LAZY: The associated entity is loaded lazily when the association is accessed.
1
2
@OneToMany(cascade = CascadeType.ALL, mappedBy = "post", fetch = FetchType.EAGER)
private List<Comment> comments;

Here we are using FetchType.EAGER to load all associated Comment entities when a Post entity is loaded.

1
2
3
4
5
6
7
public Post getPostById(Long id) {
Post post = postRepository.findById(id).orElseThrow(() -> new RuntimeException("Post not found"));
System.out.println("Post Title: " + post.getTitle());
List<Comment> comments = post.getComments();
System.out.println("Comments: " + comments);
return post;
}

Here comments associated with the post will be loaded eagerly when the post is loaded.

If you use FetchType.LAZY, comments will be loaded lazily when you access them(calling post.getComments()).

Orphan Removal

The orphanRemoval attribute is used to specify whether to remove the associated entity when it is no longer referenced by the owning entity. The orphanRemoval attribute can be specified on the @OneToOne, @OneToMany, and @OneToOne annotations. Default value is false.

1
2
@OneToMany(cascade = CascadeType.ALL, mappedBy = "post", orphanRemoval = true)
private List<Comment> comments;

Here we are using orphanRemoval = true to remove the associated Comment entity when it is no longer referenced by the Post entity.

1
2
3
4
public void deletePost(Long id) {
Post post = postRepository.findById(id).orElseThrow(() -> new RuntimeException("Post not found"));
postRepository.delete(post);
}

When we delete a Post entity, all associated Comment entities will be deleted automatically. It is identical to using CascadeType.REMOVE in this case. You can use orphanRemoval = true or CascadeType.REMOVE to achieve the same result.

1
2
3
4
5
public void deleteComments() {
Post post = postRepository.findById(1L).orElseThrow(() -> new RuntimeException("Post not found"));
post.getComments().clear();
postRepository.save(post);
}

When we remove all comments from the comments list and save the Post entity, all associated Comment entities will be deleted from the database. If orphanRemoval = false, associated Comment entities will NOT be deleted from the database.

Reference