• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

34강 orphanRemoval 관련 질문입니다!

24.02.13 10:39 작성 24.02.13 11:57 수정 조회수 257

1


orphanRemoval 을 걸지 않았을 경우

책6을 삭제했으나, userLoanHistories를

조회해보면 여전히 책6이 존재한다.


orphanRemoval = true 을 걸었을 경우

클린 코드를 삭제했더니

조회시 사라졌다


어째서 orphanRemoval을 걸어주지 않으면

데이터베이스에 반영이 되지 않는건가요?

 

@Transactional

어노테이션으로 인해 트랜잭션이 시작될 때,

영속성 컨텍스트가 시작되고,

영속성 컨텍스트의 4대 특징 중 하나인

생성/수정 감지로 인해

user 객체의 userLoanHistories 의 값에

변경사항(removeOneHistory)이 발생하여

자동저장이 될거라고 생각했는데 말이죠..

 

실제 데이터베이스상에선 userLoanHistories

user테이블의 필드가 아니기 때문에,

영속성 컨텍스트가 관리하는 field 에 포함이 되지않는데

orphanRemoval = true 를 걸어주면

비로소 자신이 관리하는 field로 인식하는걸까요?

원리가 너무 궁금해요..


 내용추가1)

바로 다음 강의인 책 대출 리팩토링에선

orphanRemoval 같은 옵션을 주지 않고도

new userLoanHistory를 add한 내용대로 저장이 되는데,

어째서 userLoanHistory를 remove하는것은

orphanRemoval 옵션이 없으면

데이터의 변경이 일어나지않는지 궁금합니다..

 제 생각에는 User에서 userLoanHistories.add

를 하든 userLoanHistories.remove 를 하든

둘 다 UserUserLoanHistory 와의 연관관계를 이용한

데이터를 변경하기위한 접근같은데 ..

add는 되는데 remove는 안되는게 살짝 이해하기 어렵습니다 ㅠ

 


내용추가2)

returnBookloanBook , removeOneHistory

차이점이 뭘까 고민해봤는데

returnBookloanBook

UserLoanHistory라는 Entity에 접근하는 과정

(loanBook은 Entity객체생성, returnBook은 Entity의

property 수정)이 존재하고,

removeOneHistory는 Entity에 직접 접근하는게 아니라

단순히 List에 대한 변경일뿐이고, Entity에 대한 직접적인

수정이나 생성이 이뤄진것이 아니므로 데이터베이스의 변경이 일어나지 않았다.

이렇게 이해했는데 제가 이해한 내용이 맞을까요?

생각을 정리하다보니 질문이 길어진 점 죄송합니다 ㅠ

답변 1

답변을 작성해보세요.

1

안녕하세요 영후이님! 정말 좋은 질문 감사드립니다!! 😊

이런 연관관계 관련 옵션이 JPA를 알쏭달쏭 신기방기 어렵게 만드는 원인이 되죠!!

하나씩 풀어서 자세하게 설명드려 보겠습니다! 👍

 

[1. oprhanRemoval이 없으면 왜 자식 엔티티가 사라지지를 않는가]

먼저 용어부터 정리해보겠습니다!

1 : N 연관관계에서

  • 1 을 담당하는 Entity를 "부모 엔티티"

  • N 을 담당하는 Entity를 "자식 엔티티"라고 하겠습니다.

 

그리고 작성해주신 코드를 확인해보면, removeIf() 를 사용하여, 특정 조건을 만족하는 자식 엔티티를 부모 엔티티에 연결되어 있는 List 에서 제거했습니다.

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children;

public void removeChild() {
  children.removeIf(child -> 조건...);
}

이 상황에서 removeIf가 되고, @Transactional 로 인해 영속성 컨텍스트가 동작하더라도 orphanRemoval이 없으면 자식 엔티티는 사리지지 않습니다.

질문 주신 내용의 핵심은 아래와 같습니다!

분명 영속성 컨텍스트의 자동감지 기능을 통해 List 에서 Child가 제거된 것을 알아차릴 것 같은데 왜 orphanRemoval 옵션이 없으면 DB에서 자식 엔티티가 사리지지 않을까요?!

 

그 이유는 자동감지 기능이 동작해서 Child가 제거된 것을 알아차렸다고 하더라도, 해당 ChildDB에서 삭제할 수 없기 때문입니다. 삭제를 하고 싶다면, orphanRemoval 옵션을 추가해야만 하죠.

따라서 orphanRemoval 없이 removeIf를 사용해서 List에서 자식 엔티티를 제거하더라도 실제 DB에서는 데이터가 제거되지 않습니다.

 

사실 생각해보면, 단순히 removeIf 만 사용하는 것은 양방향 연관관계를 끊는 것도 아닙니다. 자식 엔티티 쪽에서는 여전히 부모 엔티티를 들고 있기 때문에 (Child 객체는 Parent 객체를 참조하고 있기 때문에) 완전히 연결이 끊긴 것도 아니죠

public class Child {
  @ManyToOne
  private Parent parent; // 여기에는 parent가 여전히 연결되어 있을겁니다
}

 

따라서 orphanRemoval 옵션을 다음과 같이 생각하시면 편합니다.

  • 부모가 자식을 일방적으로 끊었을 때도 자식을 DB에서 제거해주는 옵션!

 

 

[2. orphanRemoval 없이 자식 엔티티만 제거하고 싶으면 어떻게 해야 할까?]

그렇다면 orphanRemoval 옵션을 사용하지 않고 자식 엔티티만 제거하고 싶다면 어떻게 해야 할까요?

몇 가지 방법이 있겠지만, 가장 간단한 방법으로는 서비스 레이어 자체에서 자식 엔티티를 바로 접근해 제거하는 방법이 있습니다. 예를 들어 아래와 같은 코드를 작성하면 정상적으로 제거가 될거에요!

UserLoanHistory history = userLoanHistoryRepository.findByUserIdAndBookName(userId, bookName);
userLoanHistoryRepository.delete(history);

 

[3. orphanRemoval 은 알겠다! 그런데 자식 엔티티의 추가는 왜 동작할까?]

연관관계를 양방향으로 잘 맺으면, 즉 부모도 List에 자식을 갖고 있고, 자식 (연관관계의 주인)도 부모를 참조하고 있으면 부모만 save를 해도 자식까지 잘 저장되는 것을 확인할 수 있습니다. 이런 현상은 orphanRemoval이 없어도 왜 정상 동작할까요?

 

여기서 부모를 저장하더라도 연결된 자식까지 잘 저장되는 이유는 cascade 옵션 덕분입니다. cascade 옵션은 해당 엔티티에 무언가 변화가 있을 때 (ex. 저장) 연관관계를 맺고 있는 엔티티까지 그 변화를 전파하는 옵션입니다.

따라서 다음과 같은 순서로 부모 엔티티와 자식 엔티티가 함께 저장되는거죠

  1. cascade 옵션이 있는 상태로 부모 엔티티와 자식 엔티티를 양방향 연결합니다

  2. 이제 부모 엔티티가 저장이 됩니다.

  3. 부모 엔티티가 저장이 되면, 그 저장이 자식 엔티티까지 전파되어 연결되어 있는 자식이 저장됩니다.

 

실제로 만약 cascade 옵션을 제거하시면, 양방향 연결 이후 부모 엔티티를 저장하더라도 자식 엔티티가 저장되지 않는 모습을 확인할 수 있습니다.

 

결론적으로 이렇게 이해하시면 편하실 것 같아요!

  1. 변경 감지는 새로 생기거나 제거된 연관관계를 감지할 수는 있으나 DB에 반영하지는 않는다.

  2. 부모에서 자식을 일방적으로 끊은 경우 이를 DB에 반영하고 싶다면 orphanRemoval 옵션을 써야 한다.

  3. 부모 엔티티의 변화를 (ex. 저장) 자식 엔티티에게 까지 전파하고 싶다면, cascade 옵션을 써야 한다.

 

답변이 도움이 되었으면 좋겠습니다. 감사합니다! 🙇 🙏

영후이님의 프로필

영후이

질문자

2024.02.13

친절하고 상세한 답변 감사드립니다!!

자식 엔티티 쪽에서는 여전히 부모 엔티티를 들고 있다고 생각하니깐 이해할 수 있었어요 ㅎㅎ

설명 진짜 잘하시는거 같아요

긴 글에도 퀄리티있는 답변 감사합니다!

채널톡 아이콘