6.1 Deep dependency management

And some more useful and interesting things about @OneToMany annotations and the like. They all have 4 commonly used options:

  • cascade = CascadeType.ALL
  • orphanRemoval = true
  • fetch = FetchType.LAZY

Now we will analyze them in more detail. And we will start with the most interesting - CascadeType . This parameter determines what should happen to dependent entities if we change the main entity.

The JPA specification has the following values ​​for this parameter:

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

However, Hibernate expands this specification into three more options:

  • REPLICATE
  • SAVE_UPDATE
  • LOCK

There is, of course, a strong parallel with the database and their CONSTRANIS. However, there are also differences. Hibernate tries to hide the real work with the database as much as possible, so these Hibernate Cascades are exactly about Entity objects.

6.2 CascadeType

The cascade parameter describes what should happen to dependent objects if we change their parent (master object). Most often, this parameter is used together with annotations that describe object dependencies:

Example:

OneToOne(cascade = CascadeType.ALL)

Or like this:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})

It can also be written as a separate annotation:

@Cascade({ org.hibernate.annotations.CascadeType.ALL })

Now let's talk more about what these annotations mean.

6.3 ALL, PERSIST, MERGE

CascadeType.ALLmeans that all actions that we perform with the parent object must be repeated for its dependent objects.

CascadeType.PERSISTmeans that if we save the parent object to the database, then the same must be done with its dependent objects. Example:


@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToOne(cascade = CascadeType.PERSIST, mappedBy="task")
   private EmployeeTask task;
}

An example of working with this class:


Employee director = new Employee();
EmployeeTask task = new EmployeeTask();
director.task = task;
 
session.persist(director);
session.flush();

We save only an object of type Employee, its dependent object EmployeeTask will be saved to the database automatically.

CascadeType.MERGEmeans that if we update the parent object in the database, then the same must be done with its dependent objects.

6.4 REMOVE, DELETE, DETACH

CascadeType.REMOVEmeans that if we delete a parent object in the database, then the same must be done with its dependent objects.

CascadeType.DELETEmeans the same. These are synonyms. Just from different specifications.

CascadeType.DETACHmeans that if we remove the parent object from the session, then the same must be done with its dependent objects. Example:


@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToOne(cascade = CascadeType.DETACH, mappedBy="task")
   private EmployeeTask task;
}

An example of working with this class:


Employee director = new Employee();
EmployeeTask task = new EmployeeTask();
director.task = task;
director.task = task;
session.flush();
 
assertThat(session.contains(director)).isTrue();
assertThat(session.contains(task)).isTrue();
 
session.detach(director);
 
assertThat(session.contains(director)).isFalse();
assertThat(session.contains(task)).isFalse();

CascadeType.REFRESHand CascadeType.SAVE_UPDATEwork in the same way as we expect - they duplicate the actions that are performed with the parent object to its dependent object.

6.5 Orphan removal option

Also sometimes you might come across the parameter orphan. This is short for Orphan removal. It is used to ensure that there are no child entities without parent entities.

OneToOne(orphan = true)

If this parameter is set to true, then the child entity will be deleted if it has disappearedall links. It's not exactly the same as Cascade.REMOVE.

You may have a situation where several parent entities refer to one child. Then it is beneficial that it is not deleted along with the deletion of the parent entity, but only if all references to it are nullified.

Let's say you have a class:


@Entity
@Table(name="user")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToMany(cascade = CascadeType.ALL, orphan = true)
   @JoinColumn(name = "employee_id")
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();
}


Employee director = session.find(Employee.class, 4);
EmployeeTask task = director.tasks.get(0);
director.tasks.remove(task)
session.persist(director);
session.flush();

The EmployeeTask object will be deleted because there are no references left to it. At the same time, no one deleted the parent object.

6.6 fetch option

The fetch option allows you to control how dependent objects are loaded. It usually takes one of two values:

  • FetchType.LAZY
  • FetchType.EAGER

This is a very interesting topic with various pitfalls, so I'd better talk about it in a separate lecture.