inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]

34강. JPA 연관관계에 대한 추가적인 기능들

34강 orphanRemoval 관련 질문입니다!

해결된 질문

374

영후이

작성한 질문수 3

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에 대한 직접적인

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

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

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

java spring spring-boot jpa

답변 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 옵션을 써야 한다.

 

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

2

영후이

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

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

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

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

패키지 구분에 대해 궁금한게 있습니다

0

25

2

리액트 관련 질문이 있습니다.

0

65

2

스프링부트 버전

0

76

2

7강 강의를 들으려고 했는데 오류가 나서 서버가 안 켜지는거 같아요.

0

59

2

33강. UserLoanHistory의 관계성에 대한 질문

1

56

2

Java JDK 버전 문의의 건

0

135

2

ec2 에서 Linux버전이 달라져서 설치가 안되는것 같은데 자료 최신화좀 해주세요.

0

90

3

h2 console 접속했을 테이블 질문

1

67

1

ec2 서버에서 스프링 실행도 되고 인바운드 설정까지 했는데 index.html 안됨

0

79

2

15강. updateUser() 질문

0

56

2

깃허브 질

0

84

2

여기까지 다 끝냈다고 하셨는데

0

79

2

왜안될까요

0

72

2

MySQL 창이안ㄴ뜹니다

0

59

2

포스트맨

0

52

1

spring 개념적인 질문

0

71

2

인텔리제이 샘플코드 실행 안됨 오류

0

142

2

aws 배포할때 .env 파일에 저장한 환경변수에 관하여 여쭤볼게 있습니다

0

86

1

마이그레이션 오류입니다.

0

175

3

Whitelabel Error Page 오류가 났습니다.

0

166

2

안녕하십니까! 오류가 났습니다.. 도와주세요 ㅜㅜ

1

94

3

궁금한게 있습니다.

0

60

2

DTO 관련

0

70

2

궁금한게 있습니다!

0

69

2