Spring Data JPA - Difference Between Cascade and orphanRemoval
This post was migrated from Tistory. You can find the original here.
Related post
[Spring) JPA Entity options - lazy loading / cascading persistence / orphan entity removal (23-07-12)
2023.07.13 - [spring] - Spring) JPA Entity relationships - owner of the foreign key (23-07-11) Continuing from the previous post JPA Entity Option FetchType EAGER/LAZY (lazy loading) public @interface ManyToOne{ … FetchType fetc
cornpip.tistory.com](https://cornpip.tistory.com/32)
If the data you’re trying to delete is being referenced as a foreign key in another table, you can’t delete it.
Say the entity you want to delete is Device, and Sensor holds a foreign key to Device.
- Either delete the associated
Sensor - Or set the
Deviceforeign key on the associatedSensortoNULL
Either way, you have to break the relationship depending on Device before you can delete it.
CascadeType.REMOVE
CascadeType.REMOVE - when a Device is deleted, the associated Sensor is automatically deleted as well.
1
2
3
4
5
6
7
8
// Device.java
@OneToOne(mappedBy = "device", cascade = CascadeType.REMOVE)
private Sensor sensor;
// Sensor.java
@OneToOne
@JoinColumn(name = "device_id", referencedColumnName = "id")
private Device device;
The same applies to one-to-many relationships — just put it on the parent side.
+) It’s also fine to use it on the foreign-key-owning side. Either way, when an entity with the cascade setting is deleted, its associated entity gets deleted too.
orphanRemoval = true
Same as with REMOVE, this goes on the parent side.
| Aspect | CascadeType.REMOVE | orphanRemoval = true |
| When it triggers | When the parent entity is deleted | When the child entity is removed from the relationship with the parent |
| Main delete trigger | Deletion of the parent | The relationship with the parent being broken |
| Behavior when the relationship is broken | The child entity remains | The child entity is deleted |
| Requires explicit deletion? | Requires the parent to be explicitly deleted | Deleted automatically once the relationship is broken |
I asked GPT about the difference between remove and orphanRemoval, and it gave me the table above.
With remove, the trigger is deletion of the parent entity — if you break the relationship some other way, the orphaned entity is not deleted.
With orphanRemoval, the trigger is the relationship being broken — no matter how the relationship is severed, the orphaned entity gets deleted.
1
2
3
4
5
6
7
8
// Device.java
@OneToMany(mappedBy = "device", orphanRemoval = true, cascade = CascadeType.PERSIST)
private List<Sensor> sensors = new ArrayList<>();
// Sensor.java
@ManyToOne
@JoinColumn(name = "device_id", referencedColumnName = "id")
private Device device;
Let’s verify this.
1
2
3
4
5
6
7
8
9
10
11
12
13
// TestCode
@Test
@Transactional
@Rollback(value = false)
void delete() {
Optional<Device> byId = deviceRepository.findById(4L);
if (byId.isPresent()) {
Device device = byId.get();
List<Sensor> sensors = device.getSensors();
sensors.set(1, null);
deviceRepository.save(device);
}
}
If we break the relationship by setting a null value instead of deleting the parent entity:
- With CascadeType.REMOVE, the relationship is broken but the now-unused
Sensoris not deleted. - With orphanRemoval = true, the orphaned
Sensoris deleted.
Whatever business you’re providing a computerized solution for, you’ll typically end up using a relational DB, and that brings a lot of relationships along with it.
Along the way, it’s easy to forget to wire up an FK that should be connected, or forget to break an FK that should be broken.
The more complex the ERD, the longer it takes just to reproduce the CRUD flows — but writing tests still seems to be the best way to catch the parts you’d otherwise miss.