✅casecade = CascadeType.REMOVE vs orphanRemoval = true
✔️cascade = CascadeType.REMOVE
정의 및 특징
부모 엔티티가 삭제될 때 자식 엔티티도 자동으로 삭제
- cascade가 제대로 동작하기 위해서는, 자식 엔티티가 영속성 컨텍스트에 포함되어 있어야 합니다.
→ 만약 fetch.LAZY 로딩일 때, 부모 엔티티만 영속성 컨텍스트 캐시에 존재하고
자식 엔티티는 존재하지 않는다면 Casecade.REMOVE가 정상적으로 작동되지 않습니다. - 즉, 부모, 자식 엔티티 모두 영속성 컨텍스트 상에 존재한다고 가정했을 때, 부모 엔티티를 삭제하면 자식 엔티티도 삭제됩니다.
예시
//부모 엔티티
@Entity
public Parent {
@Id @GeneratedValue(strategy = GenraionType.IDENTITY)
private Long id;
@OneToMany(mappedBy="parent",
fetch = FetchType.LAZY,
cascade=CascadeType.REMOVE)
private List<Child> children = new ArrayList<>();
}
//자식 엔티티
@Entity
public Child {
@Id @GeneratedValue(strategy = GenraionType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name="parent_id")
private Parent parent;
}
//실행
public class ParentService {
@Autowired
private ParentRepository parentRepository;
@Transactional
public void deleteParent(Long parentId) {
Parent parent = parentRepository.findById(parentId).orElseThrow();
// 자식 엔티티들을 영속성 컨텍스트에 포함시킴
parent.getChildren().size();
parentRepository.delete(parent);
}
- .size()를 사용하는 대신 @EntityGraph나 Join Fetch 등을 사용하는 방법이 있으나,
개념적인 공부를 위해서 예시에서는 size()를 이용하여 자식 엔티티를 미리 로딩하여 영속성 컨텍스트에 포함시켰습니다. - 위의 예시에서 parent와 children은 모두 영속성 컨텍스트 상에 포함되어 있습니다.
→ 그러므로 parentRepository.delete(parent) 를 했을 때 children도 삭제됩니다.
//실행
public class ParentService {
@Autowired
private ParentRepository parentRepository;
@Transactional
public void deleteParent(Long parentId) {
Parent parent = parentRepository.findById(parentId).orElseThrow();
//parent.getChildren().size();
parentRepository.delete(parent);
}
- 만약, 영속성 컨텍스트 상에 chidleren 이 포함되어 있지 않다면, delete 메서드를 수행하였을 때 부모 엔티티만 삭제되고 자식 엔티티들은 남게됩니다.
- 또는 DB에서 참조 무결성 제약 조건이 설정되어 있다고 가정할 경우 DB 쪽에서 Foreign Key constraint 에러 발생할 수 있습니다.
그렇다면 이러한 문제를 해결하기 위해서 어떻게 해야 할까요?
cascade와 같이 언급되곤 하는 orphanRemoval을 이용하여 구현해보겠습니다.
✔️orphanRemoval = true
정의 및 특징
부모 자식 관계에서 부모 엔티티가 자식 엔티티와의 관계를 끊어낼때, 자식 엔티티를 자동으로 삭제해주는 기능
- 끊어내기 전에 자식 엔티티는 영속성 컨텍스트에 포함되어야 함
- 부모 자식 관계를 끊어내는 방법들은 다음과 같습니다.
(부모는 자식 Collection을 가졌고 , 자식은 부모 객체 참조를 가졌다고 가정 )
→ 부모 엔티티가 가진 자식 컬렉션에서 remove()를 이용하여 제거
→ 부모 엔티티가 가진 자식 컬렉션을 clear()를 이용하여 제거
→ 자식 엔티티가 가진 부모 객체의 참조를 null로 세팅하여 참조 관계 제거
예시
// 부모 엔티티
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent",
fetch = FetchType.LAZY,
orphanRemoval = true)
private List<Child> children = new ArrayList<>();
}
// 자식 엔티티
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
}
// 실행
public class ParentService {
@Autowired
private ParentRepository parentRepository;
@Transactional
public void deleteParent(Long parentId) {
Parent parent = parentRepository.findById(parentId).orElseThrow();
// 자식 엔티티들을 영속성 컨텍스트에 포함시킴
parent.getChildren().size();
// 부모 엔티티가 가진 자식 컬렉션에서 remove()를 이용하여 제거
Child childEntity = parent.getChildren().get(0);
parent.getChildren().remove(childEntity);
// 부모 엔티티가 가진 자식 컬렉션을 clear()를 이용하여 제거
parent.getChildren().clear();
// 자식 엔티티가 가진 부모 객체의 참조를 null로 세팅하여 참조 관계 제거
childEntity.setParent(null);
}
}
- parent 와 child 간의 "관계" 가 끊어지면 orphanRemoval = true 에 의해 DB 상에서 자식 엔티티에 해당 하는 정보가 삭제됩니다.
그렇다면 위에서 언급했던 "자식 엔티티가 영속성 컨텍스트에 포함되어 있지 않은 상태에서, 부모 엔티티를 delete 한다면 자식 엔티티를 자동으로 삭제할 수 있는가?"에 대해 orphanRemoval = true로 해결할 수 있을까요?
정답은 X 입니다.
그 이유는
1. 영속성 컨텍스트에 자식 엔티티가 포함되어 있지 않다면 애초에 orphanRemoval = true 는 적용되지 않습니다.
→ orphanRemoval = true 역시 자식 엔티티가 영속성 컨텍스트 상에 존재해야 적용됩니다.
2. 설령 영속성 컨텍스트에 자식 엔티티가 포함되어 있다 한들 부모 엔티티를 delete 한다해서 orphanRemoval = true 에 의해 자식 엔티티가 삭제되지 않습니다.
→ 이는 cascade = CascadeType.REMOVE 에 의해 적용되는 기능 입니다.
→ orphanRemoval = true 는 영속성 컨텍스트 상에서 부모 엔티티와 자식 엔티티의 관계가 끊어질 때 적용되는 것 입니다.
이를 해결하기 위해서는 EntityGraph 또는 Fetch Join을 이용하는 등 다양한 방법들을 이용하여 자식 객체를 미리 로드할 수 있습니다. 이 부분은 다음 번에 알아보도록 하겠습니다.
회고
항상 어렴풋이 이해하고 있었던 cascade와 고아 객체에 대해 제대로 정리하고 넘어가게 되어 좋았습니다.
'스파르타 > TIL' 카테고리의 다른 글
2024.09.03 | 1차 프로젝트 진행하며 고민했던 부분들 & 추후에 도전하거나 공부해보고 싶은 부분들 (1) | 2024.09.04 |
---|---|
2024.08.29 | EntityGraph 또는 Fetch Join (0) | 2024.08.29 |
2024.08.27 | DTO 와 Entity 변환 (1) | 2024.08.27 |
2024.08.26 | API 설계서 작성 가이드 (0) | 2024.08.27 |
2024.08.23 | API 명세서 작성 시 소프트 딜리트에 대한 HTTP 메서드 선택 기준 (0) | 2024.08.23 |