JPA and Hibernate Cascade Types example with Spring Boot

In this tutorial, we will see JPA and Hibernate Cascade Types example using Spring Boot and Oracle.

Basic points about JPA Cascade Types.

The CascadeType defines the set of cascadable operations for the related entity.

Hibernate JPA Cascade Types used with association mapping. For example, @OneToOne, OneToMany, @ManyToOne and @ManyToMany annotations have an attribute cascade.

The cascade is an optional element defined in @OneToOne, OneToMany, @ManyToOne and @ManyToMany annotations.

If we don’t provide any cascade type then no operations(Persist, Merge etc) would be in effect.

The main purpose of using JPA Cascade Types is we can perform save or delete( or other operation) for Parent/Owner entity child/related entity will also be saved or deleted.


@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface OneToMany {
   
    CascadeType[] cascade() default {};
    
}

In JPA below Cascade Types has been defined.

CascadeType.PERSIST  – It means if the parent entity is saved then the related entity will also be saved.
CascadeType.MERGE  – It means if the parent entity is merged then the related entity will also be merged.
CascadeType.REMOVE  – It means if the parent entity is deleted then the related entity will also be deleted.
CascadeType.DETACH  – It means if the parent entity is detached then the related entity will also be detached.
CascadeType.REFRESH  – It means if the parent entity is refreshed then the related entity will also be refreshed.
CascadeType.ALL  – It is equivalent to cascade={DETACH, MERGE, PERSIST, REFRESH, REMOVE}.

Understanding JPA CascadeType.PERSIST with example.

Let’s see about CascadeType.PERSIST. What does it do? what will happen if we don’t use CascadeType.PERSIST? Consider we have two entity Book.java, Story.java. Assume one book can have multiple stories.

Book.java(Parent entity has OneToMany association with Story)

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int bookId;

	@Column(name = "book_name")
	private String bookName;

	@OneToMany(fetch= FetchType.LAZY)
	@JoinColumn(name = "book_id",referencedColumnName="bookId")
	private List<Story> storyList = new ArrayList<>();

}

Story.java(child entity)

@Entity
public class Story{

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int id;

	@Column(name = "story_name")
	private String storyName;

}

We have below request data that we want to persist.


{
	"bookName": "Premchand's best stories",
	"storyList": [{
			"storyName": "Stories of two oxes"
		},
		{
			"storyName": "idgah"
		},
		{
			"storyName": "Poosh Ki Rat"
		}
	]
}

We have BookServiceImpl.java as below.

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

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

}

If we don’t define cascade = CascadeType.PERSIST for OneToMany association(or any another association type) then we will not able to persist Story entity along with Book entity. An exception will be thrown. In this case, we need to save Story entity separately, which we might don’t want. JPA Cascade Types example using Spring Boot.

Without cascade type.

@OneToMany( fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", referencedColumnName = "bookId")
private List<Story> storyList = new ArrayList<>();

If we will try to save Story entities along with the Book entities we will get “org.hibernate.TransientObjectException: object references an unsaved transient instance” exception.

With cascade type.

we can save Story entities along with the Book entities.

@OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", referencedColumnName = "bookId")
private List<Story> storyList = new ArrayList<>();

Hope it is clear why we do use cascade = CascadeType.PERSIST. 

The query generated in the above case(persisting Book as well Story).


Hibernate: insert into book (book_name, book_id) values (?, ?)
Hibernate: insert into story (story_name, story_id) values (?, ?)
Hibernate: insert into story (story_name, story_id) values (?, ?)
Hibernate: insert into story (story_name, story_id) values (?, ?)
Hibernate: update story set book_id=? where story_id=?
Hibernate: update story set book_id=? where story_id=?
Hibernate: update story set book_id=? where story_id=?

We have seen query for JPA and Hibernate Cascade Types example using Spring Boot.

Understanding JPA CascadeType.MERGE with example.

Now we will see about CascadeType.MERGE. What will happen if we don’t use CascadeType.MERGE with OneToMany annotation? 

If we don’t use CascadeType.Merge then we will not able to update child entity along with the parent. For example, we have only CascadeType.PERSIST in OneToMany annotation.

@OneToMany(cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", referencedColumnName = "bookId")
private List<Story> storyList = new ArrayList<>();

Request data for save operation.

{
	"bookName": "Premchand's best stories",
	"storyList": [{
		"storyName": "Stories of two oxes"
		},
		{
		"storyName": "idgah"
		},
		{
		"storyName": "Poosh Ki Rat"
		}
	]
}

We will able to persist both entities at a time but will not able to update the child entity along with the parent.

POST – http://localhost:9091/book/savebook

JPA Cascade Types example using Spring Boot

PUT – http://localhost:9091/book/update

Request Data- The saved entity we will try to update.

{
    "bookId": 1,
    "bookName": "Premchand's best stories_new",
    "storyList": [
        {
            "storyId": 2,
            "storyName": "Stories of two oxes_new"
        }
    ]
}
JPA Cascade Types example using Spring Boot

But if we use CascadeType.MERGE with OneToMany annotation then we will able to update child entity as well.

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", referencedColumnName = "bookId")
private List<Story> storyList = new ArrayList<>();

Observe the response data this time bookName and storyName both get updated.

The query generated without using CascadeType.MERGE.

Hibernate: select book0_.book_id as book_id1_0_0_, book0_.book_name as book_name2_0_0_ from book book0_ where book0_.book_id=?

Hibernate: select storylist0_.book_id as book_id3_1_0_, storylist0_.story_id as story_id1_1_0_, storylist0_.story_id as story_id1_1_1_, storylist0_.story_name as story_name2_1_1_ from story storylist0_ where storylist0_.book_id=?

Hibernate: update book set book_name=? where book_id=?

The query generated with CascadeType.MERGE.

Hibernate: select book0_.book_id as book_id1_0_1_, book0_.book_name as book_name2_0_1_, storylist1_.book_id as book_id3_1_3_, storylist1_.story_id as story_id1_1_3_, storylist1_.story_id as story_id1_1_0_, storylist1_.story_name as story_name2_1_0_ from book book0_ left outer join story storylist1_ on book0_.book_id=storylist1_.book_id where book0_.book_id=?

Hibernate: update story set story_name=? where story_id=?

Hibernate: update book set book_name=? where book_id=?

Did you notice update query executed for CascadeType.MERGE? Now we will see JPA and Hibernate Cascade Types example using Spring Boot CascadeType.DELETE example.

Understanding JPA CascadeType.REMOVE with example.

Let’s see about CascadeType.REMOVE. What will happen if we don’t use CascadeType.REMOVE with OneToMany annotation? 

If we don’t use CascadeType.REMOVE then we will not able to delete child entity along with the parent. For example, we have only CascadeType.PERSIST in OneToMany annotation.

@OneToMany(cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", referencedColumnName = "bookId")
private List<Story> storyList = new ArrayList<>();

We will able to persist both entities at a time but will not able to delete the child entity along with the parent.

POST – http://localhost:9091/book/savebook

Hibernate JPA Cascade Types example using Spring Boot

DELETE- http://localhost:9091/book/delete

We have response without any but both entities (Book and Story) has been deleted from Database? The answer is the only Book will get deleted here not Story. Since we are not using CascadeType.REMOVE.

The query generated in the above case without using CascadeType.REMOVE.

Hibernate: select book0_.book_id as book_id1_0_0_, book0_.book_name as book_name2_0_0_ from book book0_ where book0_.book_id=?

Hibernate: select storylist0_.book_id as book_id3_1_0_, storylist0_.story_id as story_id1_1_0_, storylist0_.story_id as story_id1_1_1_, storylist0_.story_name as story_name2_1_1_ from story storylist0_ where storylist0_.book_id=?

Hibernate: update story set book_id=null where book_id=?

Hibernate: delete from book where book_id=?

Let’s use CascadeType.REMOVE.

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", referencedColumnName = "bookId")
private List<Story> storyList = new ArrayList<>();

The query generated with CascadeType.REMOVE.

Hibernate: select book0_.book_id as book_id1_0_1_, book0_.book_name as book_name2_0_1_, storylist1_.book_id as book_id3_1_3_, storylist1_.story_id as story_id1_1_3_, storylist1_.story_id as story_id1_1_0_, storylist1_.story_name as story_name2_1_0_ from book book0_ left outer join story storylist1_ on book0_.book_id=storylist1_.book_id where book0_.book_id=?

Hibernate: update story set book_id=null where book_id=?

Hibernate: delete from story where story_id=?

Hibernate: delete from book where book_id=?

Understanding JPA CascadeType.ALL with example.

The @OneToMany(cascade  = CascadeType.ALL) is equivalent to @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH,
CascadeType.DETACH }, fetch = FetchType.LAZY).

Both code snippet will behave same.

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REMOVE,
CascadeType.REFRESH }, fetch = FetchType.LAZY)
@JoinColumn(name = “book_id”, referencedColumnName = “bookId”)
private List<Story> storyList = new ArrayList<>();


@OneToMany(cascade = { CascadeType.ALL}, fetch = FetchType.LAZY)
@JoinColumn(name = “book_id”, referencedColumnName = “bookId”)
private List<Story> storyList = new ArrayList<>();

JPA Cascade Types example for OneToMany association.

Open eclipse and create maven project, Don’t forget to check ‘Create a simple project (skip)’click on next. Fill all details(GroupId – cascadetype, ArtifactId – cascadetype and name – cascadetype) and click on finish. Keep packaging as the jar.

Modify the pom.xml with the below code.

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>cascadetype</groupId>
	<artifactId>cascadetype</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>cascadetype</name>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>

		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>

			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<fork>true</fork>
					<executable>C:\Program Files\Java\jdk1.8.0_131\bin\javac.exe</executable>
				</configuration>
			</plugin>


		</plugins>
	</build>
</project>

Note – In pom.xml we have defined javac.exe path in configuration tag. You need to change accordingly i.e where you have installed JDK.

If you see any error for oracle dependency then follow these steps.

JPA CascadeType PERSIST Example Using Spring Boot

Define entity class i.e Book.java and Story.java.

Book.java

package com.onetoonehibernatejpa.entity;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int bookId;

	@Column(name = "book_name")
	private String bookName;

	@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, fetch = FetchType.LAZY)
	@JoinColumn(name = "book_id", referencedColumnName = "bookId")
	private List<Story> storyList = new ArrayList<>();

	public int getBookId() {
		return bookId;
	}

	public void setBookId(int bookId) {
		this.bookId = bookId;
	}

	public String getBookName() {
		return bookName;
	}

	public void setBookName(String bookName) {
		this.bookName = bookName;
	}

	public List<Story> getStoryList() {
		return storyList;
	}

	public void setStoryList(List<Story> storyList) {
		this.storyList = storyList;
	}

}

Story.java

package com.hibernatejpa.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Story {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int storyId;

	@Column(name = "story_name")
	private String storyName;

	public int getStoryId() {
		return storyId;
	}

	public void setStoryId(int storyId) {
		this.storyId = storyId;
	}

	public String getStoryName() {
		return storyName;
	}

	public void setStoryName(String storyName) {
		this.storyName = storyName;
	}

}

Define the repository interface extending CrudRepository.

BookRepository.java

package com.hibernatejpa.repository;

import java.io.Serializable;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.hibernatejpa.entity.Book;
@Repository
public interface BookRepository extends CrudRepository<Book,Serializable> {
	public Book findByBookId(int bookId);
}

Define service interface i.e BookService.java

package com.onetoonehibernatejpa.service;

import org.springframework.stereotype.Component;

import com.onetoonehibernatejpa.entity.Book;

@Component
public interface BookService {
	public Book saveBook(Book book);
	public Book findByBookId(int bookId);
	public Book deleteBook(Book book);
}

Define service implementation class.

BookServiceImpl.java

package com.onetoonehibernatejpa.impl;

import java.util.Optional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.onetoonehibernatejpa.entity.Book;
import com.onetoonehibernatejpa.repository.BookRepository;
import com.onetoonehibernatejpa.repository.StoryRepository;
import com.onetoonehibernatejpa.service.BookService;

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

	@Autowired
	private StoryRepository storyRepository;

	@PersistenceContext
	private EntityManager entityManager;

	@Transactional
	public Book saveBook(Book book) {
		book = bookRepository.save(book);
		return book;
	}
	
	@Transactional
	public Book deleteBook(Book book) {
		bookRepository.delete(book);
		return book;
	}

	public Book findByBookId(int bookId) {
		Optional<Book> bookResponse = bookRepository.findById(bookId);
		Book book = bookResponse.get();
		return book;
	}
}

Note – See here more about @Component, @Controller, @Service and @Repository annotations here.

Define the controller class or endpoint.

BookController.java

package com.onetoonehibernatejpa.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.onetoonehibernatejpa.entity.Book;
import com.onetoonehibernatejpa.service.BookService;
 
@RestController
@RequestMapping(value = "/book")
public class BookController {
	
	@Autowired
	private BookService bookService;
	
	
	@RequestMapping(value = "/savebook",method = RequestMethod.POST)
	@ResponseBody
    public Book saveBook(@RequestBody Book book) {
		Book bookResponse = bookService.saveBook(book);
		return bookResponse;
	}
	
	@RequestMapping(value = "/update", method = RequestMethod.PUT)
	@ResponseBody
	public Book updateBook(@RequestBody Book book) {
		Book bookResponse = (Book) bookService.saveBook(book);
		return bookResponse;
	}
	
	@RequestMapping(value = "/delete", method = RequestMethod.DELETE)
	@ResponseBody
	public Book deleteBook(@RequestBody Book book) {
		Book bookResponse = (Book) bookService.deleteBook(book);
		return bookResponse;
	}
	
	@RequestMapping(value = "/{bookId}",method = RequestMethod.GET)
    @ResponseBody
    public Book getBookDetails(@PathVariable int bookId) {
		Book bookResponse = bookService.findByBookId(bookId);
		
		return bookResponse;
	}
	
	
}

Note – See more details about @Controller and RestController here.

Define the JpaConfig.java

package com.hibernatejpa.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories(basePackages = "com.hibernatejpa.repository")
public class JpaConfig {

}

Step 12 – Define the SpringMain.java

package com.hibernatejpa.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages="com.hibernatejpa.*")
@EntityScan("com.hibernatejpa.*")
public class SpringMain {
	public static void main(String[] args) {

        SpringApplication.run(SpringMain.class, args);
    }

}

And finally, we have an application.properties file where we have database details.

application.properties

# Connection url for the database
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=SYSTEM
spring.datasource.password=oracle3
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# Show or not log for each sql query
spring.jpa.show-sql = true
 
 
spring.jpa.hibernate.ddl-auto =create
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
 
server.port = 9091

Let’s deploy the application running SpringMain class as a java application.

Sample request JSON data-

{
	"bookName": "Premchand's best stories",
	"storyList": [{
			"storyName": "Stories of two oxes"
		},
		{
			"storyName": "idgah"
		},
		{
			"storyName": "Poosh Ki Rat"
		}
	]
}

Persisting/merging/deleting child/related entity along with the parent without using JPA Cascade Types.

Since cascade is an optional element, we don’t want to use JPA CascadeTypes with @OneToMany modify Book.java and BookServiceImpl as below and Define new repository StoryRepository class.

If we look into @OneToMany annotation the cascade is an optional element. It means it’s not mandatory to use JPA CascadeType PERSIST/MERGE or any other CascadeType. If we don’t want to use JPA CascadeTypes we can’t (depends on the scenario) but we need to save/update/delete Story entity explicitly. For example below code will work fine even if you don’t use CascadeType.PERSIST and CascadeType.MERGE along with @OneToMany.

BookServiceImpl.java

package com.onetoonehibernatejpa.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.onetoonehibernatejpa.entity.Book;
import com.onetoonehibernatejpa.repository.BookRepository;
import com.onetoonehibernatejpa.repository.StoryRepository;
import com.onetoonehibernatejpa.service.BookService;

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

	@Autowired
	private StoryRepository storyRepository;

	@Transactional
	public Book saveBook(Book book) {
		storyRepository.saveAll(book.getStoryList());
		book = bookRepository.save(book);
		return book;
	}

	public Book findByBookId(int bookId) {
		Book book = bookRepository.findByBookId(bookId);
		return book;
	}
}

Book.java

package com.onetoonehibernatejpa.entity;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int bookId;

	@Column(name = "book_name")
	private String bookName;

	@OneToMany(fetch = FetchType.LAZY)
	@JoinColumn(name = "book_id", referencedColumnName = "bookId")
	private List<Story> storyList = new ArrayList<>();
//getter and setter

}

We need t define additional repository.

StoryRepository.java

package com.onetoonehibernatejpa.repository;

import java.io.Serializable;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.onetoonehibernatejpa.entity.Story;

@Repository
public interface StoryRepository extends CrudRepository<Story, Serializable> {

}

That’s all about JPA and Hibernate CascadeTypes Example Using Spring Boot.

Hibernate also provides different cascade types as below. The enum CascadeType is defined in org.hibernate.annotations package.

public enum CascadeType {
	
	ALL,
	
	PERSIST,
	
	MERGE,
	
	REMOVE,
	
	REFRESH,
	
	SAVE_UPDATE,
	
	EVICT,

	DETACH
}

Here we will see how to use Hibernate cascade type with JPA @OneToOne.

Modify Book.java as below.

package com.onetoonehibernatejpa.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

import org.hibernate.annotations.Cascade;

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int bookId;

	@Column(name = "book_name")
	private String bookName;

	@OneToMany
	@Cascade(org.hibernate.annotations.CascadeType.PERSIST)
	@JoinColumn(name = "storyid")
	private Story story;

	public Story getStory() {
		return story;
	}

	public void setStory(Story story) {
		this.story = story;
	}

	public int getBookId() {
		return bookId;
	}

	public void setBookId(int bookId) {
		this.bookId = bookId;
	}

	public String getBookName() {
		return bookName;
	}

	public void setBookName(String bookName) {
		this.bookName = bookName;
	}

}

Rest of code would be the same.

That’s all about JPA and Hibernate Cascade Types example using Spring Boot.

JPA Cascade Types docs.

Summary –

If two entities(Book and Story) are in OneToMany relationship and we want to save Book and Story entity. There are two ways to do that. we can save Book and Story entity separately or we save Book entity and since Book and Story are in OneToOne or OneToMany relationships, Story entity should also save along with Book. 

The CascadeType.PERSIST is all about second ways. If we use CascadeType.PERSIST no need to save child/related entity separately. The similar concept applies to other CascadeTypes.