Что делает CascadeType.REMOVE в JPA/Hibernate?

«Что делает CascadeType.REMOVE в JPA/Hibernate?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

CascadeType.REMOVE — это параметр каскадирования операций в JPA, который указывает, что при удалении родительской сущности должны быть автоматически удалены все связанные дочерние сущности.

Базовый пример:

@Entity
public class Department {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "department", cascade = CascadeType.REMOVE)
    private List<Employee> employees; // При удалении Department удалятся все Employee
}

@Entity
public class Employee {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    private Department department;
}

Что происходит при вызове:

Department dept = entityManager.find(Department.class, 1L);
entityManager.remove(dept); // Автоматически удалит всех связанных employees
// Эквивалентно:
// for (Employee emp : dept.getEmployees()) {
//     entityManager.remove(emp);
// }
// entityManager.remove(dept);

Отличие от orphanRemoval = true:

// CascadeType.REMOVE vs orphanRemoval
@Entity
public class Parent {
    @OneToMany(mappedBy = "parent", 
               cascade = CascadeType.REMOVE) // Удаление только при удалении Parent
    private List<Child> children;
}

@Entity  
public class Parent2 {
    @OneToMany(mappedBy = "parent",
               orphanRemoval = true) // Удаление Child также при отсоединении от Parent
    private List<Child> children;
}

// orphanRemoval = true более агрессивен:
Parent2 parent = em.find(Parent2.class, 1L);
parent.getChildren().remove(0); // Child сразу удаляется из БД!

Каскадирование на нескольких уровнях:

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "order", cascade = CascadeType.REMOVE)
    private List<OrderItem> items;
}

@Entity
public class OrderItem {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(cascade = CascadeType.REMOVE) // Каскад на второй уровень
    private List<ItemDetail> details;
}

// Удаление Order → удаление всех OrderItem → удаление всех ItemDetail

Ограничения и особенности:

  1. Работает только с отношениями, где сущность — владелец связи
  2. Не работает с @ManyToMany — нужно использовать промежуточную сущность
  3. Может нарушать foreign key constraints, если не настроены каскады на уровне БД

Лучшие практики:

// 1. Используйте осторожно — неожиданные каскадные удаления
@OneToMany(mappedBy = "department", 
           cascade = {CascadeType.PERSIST, CascadeType.MERGE}) // Без REMOVE!

// 2. Явное удаление через репозиторий безопаснее
@Service
public class DepartmentService {
    @Transactional
    public void deleteDepartment(Long deptId) {
        // Явный контроль над удалением связанных данных
        departmentRepo.deleteEmployeesByDepartmentId(deptId);
        departmentRepo.deleteById(deptId);
    }
}

// 3. Каскад на уровне БД (более эффективно)
ALTER TABLE employee 
ADD CONSTRAINT fk_department 
FOREIGN KEY (department_id) 
REFERENCES department(id) 
ON DELETE CASCADE;

Полный список CascadeType:

  • PERSIST — каскадное сохранение
  • MERGE — каскадное обновление
  • REMOVE — каскадное удаление
  • REFRESH — каскадное обновление из БД
  • DETACH — каскадное отсоединение
  • ALL — все вышеперечисленные