JPA + Hibernate Cascade Scenarios

After playing with entity relationships in Hibernate, I wanted to write my notes about the Cascade options and some scenarios for each one. At the end of this post, find a table to match the Cascade Types in Hibernate to JPA and the link to the source code in github.

In the example, we only use a ManyToOne relationship: a role can have many users and an user can have only one role.

package com.sgitario.hibernate.cascade.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class UserWithNoCascade {
 private Integer id;
 private String firstname;
 private String lastname;

 /** Omit Constructor **/

 @Id
 @GeneratedValue
 public Integer getId() {
  return id;
 }

 /** Omit Setters **/

 @Column(name = "C_FIRSTNAME")
 public String getFirstname() {
  return firstname;
 }

 @Column(name = "C_LASTNAME")
 public String getLastname() {
  return lastname;
 }

 /**
  * Relationship
  */

 private Role role;

 @ManyToOne
 @JoinColumn(name = "role")
 public Role getRole() {
  return role;
 }

 public void setRole(Role role) {
  this.role = role;
 }

}

None Cascade

This is the scenario:

 @Test
 public void scenario() {
  UserWithNoCascade user = new UserWithNoCascade("Dave", "Matthews");
  user.setRole(saveRole(new Role("Manager"))); // 1 save action: 1 insert
  user = saveUser(user); // 1 save action: 1 insert

  user.setFirstname("New Name 2");
  user = saveUser(user); // 1 save action: 2 queries, 1 update
  user.getRole().setRoleName("Employer 2");
  saveRole(user.getRole()); // 1 save action: 1 query, 1 update
 }
Queries: 
*****: Saving Role
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Role (C_ROLE_NAME, id) values (?, ?)
*****: Saving User
Hibernate: call next value for hibernate_sequence
Hibernate: insert into UserWithNoCascade (C_FIRSTNAME, C_LASTNAME, role, id) values (?, ?, ?, ?)
*****: Saving User #1
Hibernate: select userwithno0_.id as id1_3_0_, userwithno0_.C_FIRSTNAME as C_FIRSTN2_3_0_, userwithno0_.C_LASTNAME as C_LASTNA3_3_0_, userwithno0_.role as role4_3_0_ from UserWithNoCascade userwithno0_ where userwithno0_.id=?
Hibernate: select role0_.id as id1_0_0_, role0_.C_ROLE_NAME as C_ROLE_N2_0_0_ from Role role0_ where role0_.id=?
Hibernate: update UserWithNoCascade set C_FIRSTNAME=?, C_LASTNAME=?, role=? where id=?
*****: Saving Role
Hibernate: select role0_.id as id1_0_0_, role0_.C_ROLE_NAME as C_ROLE_N2_0_0_ from Role role0_ where role0_.id=?
Hibernate: update Role set C_ROLE_NAME=? where id=?

With no cascade, you need to save/update the entity and the child entity everytime you make an action. Also, the parent and child entities will be readded to the managed context after a save/update (see #1).

Persist Cascade

This is the relationship in the entity:

 @ManyToOne(cascade = CascadeType.PERSIST)
 @JoinColumn(name = "role")
 public Role getRole() {
  return role;
 }

This is the scenario:

 @Test
 public void scenario() {
  UserWithPersistCascade user = new UserWithPersistCascade("Dave", "Matthews");
  user.setRole(new Role("Manager"));
  user = saveUser(user); // 1 save action: 2 inserts

  user.setFirstname("New Name 1");
  user = saveUser(user); // 1 save action: 2 queries, 1 update
  user.getRole().setRoleName("Employer 1");
  saveRole(user.getRole()); // 1 save action: 1 query, 1 update
 }

Queries:

*****: Saving User #1
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Role (C_ROLE_NAME, id) values (?, ?)
Hibernate: insert into UserWithPersistCascade (C_FIRSTNAME, C_LASTNAME, role, id) values (?, ?, ?, ?)
*****: Saving User #2
Hibernate: select userwithpe0_.id as id1_4_0_, userwithpe0_.C_FIRSTNAME as C_FIRSTN2_4_0_, userwithpe0_.C_LASTNAME as C_LASTNA3_4_0_, userwithpe0_.role as role4_4_0_ from UserWithPersistCascade userwithpe0_ where userwithpe0_.id=?
Hibernate: select role0_.id as id1_0_0_, role0_.C_ROLE_NAME as C_ROLE_N2_0_0_ from Role role0_ where role0_.id=?
Hibernate: update UserWithPersistCascade set C_FIRSTNAME=?, C_LASTNAME=?, role=? where id=?
*****: Saving Role #2
Hibernate: select role0_.id as id1_0_0_, role0_.C_ROLE_NAME as C_ROLE_N2_0_0_ from Role role0_ where role0_.id=?
Hibernate: update Role set C_ROLE_NAME=? where id=?

With persist cascade, the related entities will be saved/updated when the parent is saved #1, but not updated #2.

Merge Cascade

This is the relationship in the entity:

 @ManyToOne(cascade = CascadeType.MERGE)
 @JoinColumn(name = "role")
 public Role getRole() {
  return role;
 }

This is the scenario:

 @Test
 public void scenario() {
  UserWithMergeCascade user = new UserWithMergeCascade("Dave", "Matthews");
  Role role = saveRole(new Role("Manager")); // 1 save action: 1 insert
  user.setRole(role);
  user = saveUser(user);// 1 save action: 1 insert

  user.setFirstname("New Name 1");
  user.getRole().setRoleName("Employer 1");
  user = saveUser(user);// 1 save action: 1 query, 2 updates
 }

Queries:

*****: Saving Role
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Role (C_ROLE_NAME, id) values (?, ?)
*****: Saving User
Hibernate: call next value for hibernate_sequence
Hibernate: insert into UserWithMergeCascade (C_FIRSTNAME, C_LASTNAME, role, id) values (?, ?, ?, ?)
*****: Saving User #1
Hibernate: select userwithme0_.id as id1_2_1_, userwithme0_.C_FIRSTNAME as C_FIRSTN2_2_1_, userwithme0_.C_LASTNAME as C_LASTNA3_2_1_, userwithme0_.role as role4_2_1_, role1_.id as id1_0_0_, role1_.C_ROLE_NAME as C_ROLE_N2_0_0_ from UserWithMergeCascade userwithme0_ left outer join Role role1_ on userwithme0_.role=role1_.id where userwithme0_.id=?
Hibernate: update Role set C_ROLE_NAME=? where id=?
Hibernate: update UserWithMergeCascade set C_FIRSTNAME=?, C_LASTNAME=?, role=? where id=?

With merge cascade as in no cascade, you need to save every entity individually. Then the child entities will be updated when the parent entity is merged/saved, see #1.

Remove Cascade

This is the relationship in the entity:

 @ManyToOne(cascade = CascadeType.REMOVE)
 @JoinColumn(name = "role")
 public Role getRole() {
  return role;
 }

This is the scenario:

 @Test
 public void scenario() {
  UserWithRemoveCascade user = new UserWithRemoveCascade("Dave", "Matthews");
  Role role = new Role("Manager");
  role = saveRole(role);
  user.setRole(role);
  user = saveUser(user);

  deleteUser(user);
 }

Queries:

*****: Saving Role
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Role (C_ROLE_NAME, id) values (?, ?)
*****: Saving User
Hibernate: call next value for hibernate_sequence
Hibernate: insert into UserWithRemoveCascade (C_FIRSTNAME, C_LASTNAME, role, id) values (?, ?, ?, ?)
*****: Removing User #1
Hibernate: select userwithre0_.id as id1_4_0_, userwithre0_.C_FIRSTNAME as C_FIRSTN2_4_0_, userwithre0_.C_LASTNAME as C_LASTNA3_4_0_, userwithre0_.role as role4_4_0_ from UserWithRemoveCascade userwithre0_ where userwithre0_.id=?
Hibernate: select role0_.id as id1_0_0_, role0_.C_ROLE_NAME as C_ROLE_N2_0_0_ from Role role0_ where role0_.id=?
Hibernate: delete from UserWithRemoveCascade where id=?
Hibernate: delete from Role where id=?

With remove cascade, the child entities will be removed if the parent is deleted, see #1. Bear in mind that the use case of the remove cascade is when removing parent entities. Another scenario is when we remove the relationship (when it’s a list, doing a list.remove(x)), for this we have the remove_orphan.

 @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
 @JoinColumn(name = "roles")
 public List getRoles() {
  return roles;
 }

More Cascade options

From JPA:

  • ALL: All cascade types in one.
  • DETACH: It detaches all child entities if a “manual detach” occurs in parent entity. It implies more queries.
  • REFRESH: When the entity manager has references of the parent entity in memory and this parent entity is saved/merged, then all these references and its child relationships will be refreshed (retrieved again from DB).

From Hibernate, the more popular JPA provider, there are some more options:

  • SAVE_ACTION: When the Hibernate session is called with saveOrUpdate.
  • REPLICATE: When the parent entity state needs to be mirrored between two distinct DataSources
  • LOCK: More about this lock and scenarios here.

Conclusion

Analyze the implications of using one cascade type or another. A wrong cascade type might cause lot of unnecessary queries in database.

Source Code in GitHub

[ Hibernate ]