JPA 양방향 연관관계 매핑과 연관관계의 주인

ㅎㅎ·2023년 10월 5일
0

양방향 연관관계란 무엇?

  • 양방향 연관관계란 연관관계가 있는 두 엔티티 간에 양쪽 방향에서 모두 객체 탐색이 가능하도록 연관관계를 설정하는 것을 말함

  • 회원-팀 엔티티가 있을 때, 회원 객체가 팀 필드를 가지고 있어 회원 객체로부터 팀을 조회할 수 있고, 팀 객체가 회원 필드를 가지고 있어 팀 객체로부터 회원을 조회할 수 있도록 함

  • 실제로는 회원 -> 팀 참조와 팀 -> 회원 참조가 가능한 단방향 매핑이 모두 있는 것이지만 이를 양방향이라고 일컬음


양방향 연관관계 설정 방법

  • 양방향 매핑할 객체의 필드에 연관관계 어노테이션을 달고, (mappedBy = 반대편 객체 필드 명)을 달아줌.

  • mappedBy를 명시해 준 엔티티가 아닌 반대편 엔티티가 연관관계의 주인이 됨

  • 연관관계의 주인인 엔티티만 연관관계에 있는 해당 객체를 등록, 수정, 삭제할 수 있음

  • 주인이 아닌 엔티티는 객체 조회만 가능함. 등록, 수정, 삭제한다고 해도 DB에 반영되지 않음.


양방향 연관관계일 때 연관관계의 주인이 필요한 이유

  • 객체 지향으로 데이터를 관리하는 것과 관계형 DB로 데이터를 관리하는 것의 차이에서 기인

  • 관계형 DB는 외래키로 연관관계를 맺게됨. 그리고 JOIN을 통해서 양쪽 테이블이 합쳐짐

  • 외래키의 값이 바뀌면 해당 테이블의 값만 변경해주면 됨. 주키를 가지고 있는 테이블은 변화가 없음(주키를 가지고 있는 테이블은 외래키 테이블의 데이터를 함께 가지고 있지 않음)

  • 그러나 객체는 양방향 관계를 맺었을 때 양쪽 모두 데이터를 가지고 있음. 회원은 팀 엔티티를 물고 있고, 팀 엔티티는 회원 리스트 엔티티를 물고 있음.

  • 만약에 회원 1이 속한 팀1을 팀2로 바꾸려고 한다면, 회원 엔티티가 물고 있는 팀 필드의 값을 변경해주어야 할까? 아니면 팀1의 회원1을 삭제하고, 팀2에 회원1을 추가해야 할까?


주인은 누구로 설정할까?

  • 외래 키가 있는 곳을 주인으로 정해라(김영한님 가이드)

  • 우선 외래키가 있는 곳이 아닌 테이블을 연관관계의 주인으로 삼으면, 해당 엔티티를 바꿨을 때 다른 테이블에 업데이트 쿼리가 나감. JPA에 아주 빠삭하다면 모르겠지만 직관적이지 않고 복잠함

  • DB 입장에서 보면 외래키가 있는 곳이 무조건 다, 반대편이 1인 다대일 관계임(1대1도 있지만), N:1 관계에서 N쪽이 무조건 연관관계 주인이라고 생각하면 편함.

  • 비즈니스적으로는 아무 의미가 없음, 비즈니스적으로 다르게 해야할까 고민하지 말고, 기능상 설계를 위해 무조건 이렇게 하는 것을 추천

  • 성능 이슈도 있는데 이건 나중에 설명


양방향 연관관계 매핑에서 많이 하는 실수

  • 연관관계의 주인이 아닌 쪽에서 데이터를 등록, 수정, 삭제해봤자 JPA는 이걸 처리하지 않는다.

  • 그래서 주인이 아닌 쪽 필드는 가짜매핑이라고 봐야 한다. 조회만 가능하다.

  • 그런데 가짜매핑한 쪽의 필드를 가지고 데이터를 조작하는 실수를 많이한다.

  • 아래 코드에서 멤버의 팀 정보는 업데이트되지 않는다.

  • 아래와 같이 연관관계 주인쪽에 데이터를 조작해야줘야 함.


순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다

  • 연관관계의 주인이 아닌 쪽 엔티티가 영속성 컨텍스트에 1차 캐시로 등록되어 있는 상태에서, 연관관계 주인인 엔티티의 값을 변경한 후에 flush & clear를 해주지 않게 되면, 연관관계 주인이 아닌 쪽 데이터가 변경됐다고 생각하고 해당 엔티티를 조회해봤자 1차 캐시에 있는 변경되지 않은 엔티티가 조회되어 데이터 불일치가 발생함.

  • 테스트 케이스 작성 시에도 JPA 없이 작성하기 때문에 위 불일치를 해결해야 함.

  • 양쪽 모두 값을 추가하는 처리는 귀찮기도 하고, 깜빡할 수도 있기 때문에 아예 아래와 같이 연관관계 편의 메소드를 만들어서 해결하는 것을 추천한다.

  • 또한 setter보다 changeTeam과 같은 메소드명으로 사용하는 것을 추천.(setter 지양)


앙뱡향 매핑시 무한 루프를 조심하자

  • toString() 메소드 라이브러리를 이용해 오버라이딩하거나, lombok을 이용해서 toString() 메소드가 자동 오버라이딩되어 있을 때 양방향 매핑의 각 엔티티가 계속 순환참조하면서 StackOverFlow가 발생할 수 있음.

  • JSON 생성 라이브러리를 이용해도 엔티티를 JSON으로 바꾸면서 위와 같이 쭉 객체를 참조해 뽑아버리면서 무한루프가 발생, 보통 컨트롤러에서 응답으로 엔티티 자체를 RETURN하면 JSON으로 바꾸면서 에러가 발생

  • 하지만 컨트롤러에서 Entity 자체를 반환하지 말고, DTO로 바꿔서 반환하라는 원칙을 따르면 위 문제는 발생하지 않을 것임.

  • Entity 자체는 유지보수하며 변경될 여지가 있음. Entity 자체를 반환하면 나중에 API의 스펙 자체가 아예 바뀌어버림. DTO를 반환하면 Entity에 API가 종속적이지 않으므로 문제 발생 X


정리

  • 단방향 매핑만 해도 어차피 조회는 가능하다. 기본적으로 단방향 매핑으로 끝낸다고 생각하자. 하지만 실무에서는 JPQL을 사용하다보면 역방향 탐색을 할 일이 많음. 따라서 필요할 때 양방향 매핑을 추가하자. 어차피 양방향 매핑을 중간에 추가해 봤자 테이블의 변화는 없다.

김영한님 강의 자반 ORM 표준 JPA 프로그래밍 참고하여 작성

profile
Hello World

0개의 댓글