Spring Boot JPA Cascade

Cascade operations in JPA allow us to propagate operations from a parent entity to its related entities.

Entities

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

private String name;

@OneToMany(mappedBy = "category")
private List<Book> books;

// getters and setters
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String title;

@ManyToOne
@JoinColumn(name = "categoryId")
private Category category;

// getters and setters
}

If we don’t add cascade type, operations such as deleting or persisting on one entity will not be propagated to the related entities. For example, if we delete a Category, the associated Book entities will remain in the database, potentially leading to foreign key constraint violations.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Category techCategory = new Category();
techCategory.setName("Tech");
categoryRepo.save(techCategory);

Book book1 = new Book();
book1.setTitle("How to learn JPA");
book1.setCategory(techCategory);
bookRepo.save(book1);

Book book2 = new Book();
book2.setTitle("How to learn Java");
book2.setCategory(techCategory);
bookRepo.save(book2);

categoryRepo.deleteAll();

In the above example, deleting the Category will not delete the associated Book entities, leading to foreign key constraint violations.

CascadeType.REMOVE

To enable cascading delete operations, we can use the CascadeType.REMOVE option in the @OneToMany annotation.

1
2
3
4
5
6
public class Category {
// other fields

@OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE)
private List<Book> books;
}

With this configuration, when we delete a Category, all associated Book entities will also be deleted automatically.

1
categoryRepo.deleteAll();

This will delete the Category and all associated Book entities without any foreign key constraint violations.

CascadeType.PERSIST

Similarly, we can use CascadeType.PERSIST to propagate persist operations from a parent entity to its related entities.

1
2
3
4
5
6
public class Category {
// other fields

@OneToMany(mappedBy = "category", cascade = CascadeType.PERSIST)
private List<Book> books;
}

With this configuration, when we save a Category, all associated Book entities will also be saved automatically.

1
2
3
4
5
6
7
8
9
10
11
Category techCategory = new Category();
techCategory.setName("Tech");

Book book1 = new Book();
book1.setTitle("How to learn JPA");

Book book2 = new Book();
book2.setTitle("How to learn Java");

techCategory.setBooks(List.of(book1, book2));
categoryRepo.save(techCategory);

This will save the Category and all associated Book entities.

However, persist operations on Book entities will not automatically save the associated Category entity unless we also add CascadeType.PERSIST to the @ManyToOne annotation in the Book entity. so we need to modify the Book entity as follows:

1
2
3
4
5
6
public class Book {
// other fields
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "categoryId")
private Category category;
}

Propagating from the child entity(Book) to the parent entity(Category) is less common and is generally not recommended as it can lead to unintended side effects. It often leads to detached entity passed to persist PersistentObjectException if not handled carefully.

CascadeType.ALL

We can also use CascadeType.ALL to apply all cascade operations (PERSIST, MERGE, REMOVE, REFRESH, DETACH) to the related entities.

1
2
3
4
5
public class Category {
// other fields
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private List<Book> books;
}

Summary

The most common cascade types used in JPA are CascadeType.PERSIST and CascadeType.ALL. They are used to propagate persist and delete operations from a parent entity to its related entities.

By using cascade operations, we can simplify our code and ensure that related entities are managed consistently.