JPA / Hibernate Cascade Types Tutorial

In this tutorial, we'll explore the different cascade types provided by JPA (Java Persistence API) and Hibernate. Cascade types define how operations on a parent entity are propagated to related child entities. Understanding cascade types is crucial for managing relationships in a JPA/Hibernate-based application. We'll use the latest version of Hibernate for this tutorial.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • A Spring Boot project set up (you can create one using Spring Initializr)
  • Basic knowledge of Spring Boot, JPA, and Hibernate

Step 1: Setting Up the Project

First, create a Spring Boot project using Spring Initializr with the following dependencies:

  • Spring Web
  • Spring Data JPA
  • H2 Database (for simplicity, but you can use any database of your choice)

Project Structure

Your project structure should look like this:

src/main/java
└── com
    └── example
        └── cascade
            ├── CascadeDemoApplication.java
            ├── model
            │   ├── Author.java
            │   └── Book.java
            ├── repository
            │   ├── AuthorRepository.java
            │   └── BookRepository.java
            └── service
                ├── AuthorService.java
                └── BookService.java

Step 2: Creating Entity Classes

Let's create two entities: Author and Book. An author can have multiple books, so we need a one-to-many relationship.

Author Entity

package com.example.cascade.model;

import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Book> books = new HashSet<>();

    // Getters and Setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Book> getBooks() {
        return books;
    }

    public void setBooks(Set<Book> books) {
        this.books = books;
    }

    public void addBook(Book book) {
        books.add(book);
        book.setAuthor(this);
    }

    public void removeBook(Book book) {
        books.remove(book);
        book.setAuthor(null);
    }
}

Book Entity

package com.example.cascade.model;

import jakarta.persistence.*;

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    // Getters and Setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }
}

Step 3: Creating Repository Interfaces

Next, create repository interfaces for the Author and Book entities.

AuthorRepository

package com.example.cascade.repository;

import com.example.cascade.model.Author;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AuthorRepository extends JpaRepository<Author, Long> {
}

BookRepository

package com.example.cascade.repository;

import com.example.cascade.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {
}

Step 4: Creating Service Classes

Create service classes to handle business logic.

AuthorService

package com.example.cascade.service;

import com.example.cascade.model.Author;
import com.example.cascade.repository.AuthorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AuthorService {

    @Autowired
    private AuthorRepository authorRepository;

    public List<Author> getAllAuthors() {
        return authorRepository.findAll();
    }

    public Author getAuthorById(Long id) {
        return authorRepository.findById(id).orElse(null);
    }

    public Author saveAuthor(Author author) {
        return authorRepository.save(author);
    }

    public void deleteAuthor(Long id) {
        authorRepository.deleteById(id);
    }
}

BookService

package com.example.cascade.service;

import com.example.cascade.model.Book;
import com.example.cascade.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {

    @Autowired
    private BookRepository bookRepository;

    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }

    public Book getBookById(Long id) {
        return bookRepository.findById(id).orElse(null);
    }

    public Book saveBook(Book book) {
        return bookRepository.save(book);
    }

    public void deleteBook(Long id) {
        bookRepository.deleteById(id);
    }
}

Step 5: Understanding Cascade Types

JPA defines several cascade types:

  1. CascadeType.ALL: Propagates all operations (including remove) from the parent to the child.
  2. CascadeType.PERSIST: Propagates the persist operation from the parent to the child.
  3. CascadeType.MERGE: Propagates the merge operation from the parent to the child.
  4. CascadeType.REMOVE: Propagates the remove operation from the parent to the child.
  5. CascadeType.REFRESH: Propagates the refresh operation from the parent to the child.
  6. CascadeType.DETACH: Propagates the detach operation from the parent to the child.

In the Author entity, we used CascadeType.ALL and orphanRemoval = true:

@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Book> books = new HashSet<>();

CascadeType.ALL

This cascade type ensures that all operations performed on the Author entity (e.g., persist, merge, remove) are also applied to the associated Book entities. This means if you save or delete an Author, all associated Books will be saved or deleted as well.

Orphan Removal

The orphanRemoval attribute set to true ensures that when a Book is removed from the books set in the Author entity, it is also removed from the database. This is useful to keep the database in sync with the state of your Java objects.

Step 6: Creating the Application Class

Create the main application class to run your Spring Boot application.

package com.example.cascade;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CascadeDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(CascadeDemoApplication.class, args);
    }
}

Step 7: Testing the Application

Now that everything is set up, you can run your Spring Boot application and test the cascade types. You can use tools like Postman or cURL to interact with the RESTful API endpoints.

Example Operations

  1. Create a new author with books:

    POST /authors
    Content-Type: application/json
    
    {
        "name": "Jane Austen",
        "books": [
            {
                "title": "Pride and Prejudice"
            },
            {
                "title": "Sense and Sensibility"
            }
        ]
    }
    
  2. Get all authors:

    GET /authors
    
  3. Get an author by ID:

    GET /authors/{id}
    
  4. Delete an author by ID:

    DELETE /authors/{id}
    

Verifying Cascade Types

  • When you create an Author with books, the books should be persisted automatically due to the CascadeType.PERSIST.
  • When you delete an Author, the associated books should also be deleted due to the CascadeType.REMOVE.
  • Removing a book from the books set of an Author and then saving the Author should also remove the book from the database due to orphanRemoval = true.

Conclusion

In this tutorial, we have explored the different cascade types provided by JPA and Hibernate, and how to use them in a Spring Boot application. Understanding cascade types is crucial for managing relationships in a JPA/Hibernate-based application. By using the appropriate cascade types, you can ensure that operations on parent entities are correctly propagated to related child entities, maintaining the integrity of your data.


Comments