Skip to content

Latest commit

 

History

History
185 lines (99 loc) · 10.9 KB

5장.md

File metadata and controls

185 lines (99 loc) · 10.9 KB

5장: 연관관계 매핑 기초

객체 관계 매핑에서 가장 어려운 부분이 바로 객체 연관관계와 테이블 연관관계를 매핑하는 일입니다. 객체의 참조와 테이블의 외래 키를 매핑하는 것이 이 장의 목표입니다.


단방향 연관관계

연관관계 중에서 다대일(N:1) 단방향 관계를 가장 먼저 이해해야 합니다.

  • 회원과 팀이 있습니다.
  • 회원은 하나의 팀에만 소속될 수 있습니다.
  • 회원과 팀은 다대일 관계입니다.

스크린샷 2021-08-25 오후 11 31 38

위의 그림을 보면 회원 객체와 팀 객체는 단방향 관계입니다. 즉, Member -> Team의 조회는 가능하지만 Team -> Member를 접근하는 필드는 없습니다. 반면에 테이블 연관관계는 회원 테이블과 팀 테이블은 양방향 관계입니다. 즉, 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고 반대로 팀과 회원도 조인할 수 있습니다.

테이블과 객체는 이러한 큰 간격이 존재합니다.

스크린샷 2021-08-25 오후 11 36 28


스크린샷 2021-08-25 오후 11 36 35

Member 객체와 Team 객체를 매핑하면 위와 같이 할 수 있습니다.


스크린샷 2021-08-25 오후 11 41 09

이 상태에서 Team과 Member로 코드를 작성해보면 위에서 볼 수 있듯이 뭔가 객체지향과는 맞지 않은 부분이 존재합니다.


스크린샷 2021-08-25 오후 11 45 13

뭔가 조회하는 과정에서도 객체지향스럽지 않고 난잡하다는 느낌을 받을 수 있습니다.


스크린샷 2021-08-25 오후 11 49 20

그래서 객체지향스럽게 설계하기 위해서는 위와 같이 Member 객체가 teamId가 아니라 Team 객체를 가지도록 설계를 해보겠습니다.


스크린샷 2021-08-25 오후 11 54 11

  • 멤버가 N이고 팀이 1이기 때문에 Member 입장에서는 N:1이기 때문에 ManyToOne 어노테이션을 사용합니다.
  • @JoinColumn은 어떤 컬럼을 기준으로 JOIN 할지를 명시하는 어노테이션입니다.

스크린샷 2021-08-25 오후 11 54 56

이렇게 설계를 한 후에 위에서 작성했던 코드를 리팩터링 해보겠습니다.


스크린샷 2021-08-25 오후 11 57 54

Member에 TeamId가 아니라 Team을 저장하니까 확실히 아까보다는 좀 더 객체지향스럽고 코드도 깔끔해진 것을 볼 수 있습니다.



양방향 연관관계와 연관관계의 주인

스크린샷 2021-08-26 오전 12 04 25

위에서 보면 SQL에서는 외래키를 통해서 양쪽에서 참조할 수 있지만, 객체는 한쪽에서만 참조가 가능하다는 차이점이 있었습니다. 즉, 위의 예시로 보면 Member -> Team으로는 참조할 수 있지만, Team -> Member로는 참조할 수 없습니다. 즉, 연관관계를 하나 더 만들어야 합니다.

이렇게 양쪽해서 서로 참조하는 것을 양방향 연관관계라고 하는데요. 정확히 말하면 양방향 관계가 아니라 서로 다른 단방향 관계 2개입니다.


스크린샷 2021-08-26 오전 12 10 06

그래서 Team에서 Member를 참조하기 위해서 위와 같이 설정할 수 있습니다. 여기서 보아야 할 점은 mappedBy 라는 것인데요. 이것은 연관관계의 주인이라는 것을 알아야 합니다.


연관관계의 주인

위에서 말했던 것처럼 테이블은 외리캐 하나로 두 테이블의 연관관계를 관리합니다. 하지만 엔티티는 단방향 2개가 있어야 양방향 관계가 됩니다. 그리고 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 합니다. 이것을 연관관계의 주인이라고 합니다.



양방향 매핑 규칙

  • 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리합니다.
  • 주인이 아닌 쪽은 읽기만 가능합니다.
  • 주인은 mappedBy 속성을 사용하지 않습니다.
  • 주인이 아니면 mappedBy 속성으로 주인을 지정합니다.

스크린샷 2021-08-26 오전 12 26 08

연관관계 주인은 Many 쪽에 주고, One 쪽에는 읽기 전용으로 두는 것이 좋습니다. 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것입니다. 즉, 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 합니다.

정리하면, 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있습니다. 주인이 아닌 반대편은 읽기만 가능하고 외래키를 변경하지는 못합니다.



양방향 매핑시 가장 많이 하는 실수

스크린샷 2021-08-26 오후 12 28 30

위와 같이 Team을 통해서 Member를 추가했습니다. 이 상태로 실행을 해보겠습니다.


스크린샷 2021-08-26 오후 12 30 37

그러면 분명히 INSERT 쿼리도 2번이 실행된 것을 볼 수 있습니다. 그래서 DB에도 값이 잘 들어갔는지 확인해보겠습니다.


스크린샷 2021-08-26 오후 12 31 42

확인해보니 Member 테이블에 TEAM_ID가 null인 것을 볼 수 있습니다. 위에서 분명히 INSERT 쿼리도 실행이 되었고 값을 넣어주었던 거 같은데 말이죠..

왜 이런가 생각해보면 Member가 연관관계의 주인이고, Team은 MappedBy가 지정된 읽기 전용이기 때문에 Team을 통해서 Member를 저장하려 할 때는 제대로 저장이 되지 않는 것입니다. 그래서 이런 문제를 해결하려면 아래와 같이 수정하면 됩니다.


스크린샷 2021-08-26 오후 12 34 22

그래서 위와 같이 연관관계의 주인인 Member를 통해서 Team을 저장하도록 하면 제대로 값이 잘 들어가게 됩니다.


스크린샷 2021-08-26 오후 12 35 44



순수한 객체까지 고려한 양방향 연관관계

그렇다면 정말 연관관계의 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않아도 될까요? 객체 관점에서는 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전합니다.


스크린샷 2021-08-26 오후 12 45 16

위의 코드처럼 한쪽 방향에만 값을 넣어주고 Team에서 Member를 호출하면 값이 제대로 출력이 될까? 할 수 있지만 제대로 출력이 됩니다.


스크린샷 2021-08-26 오후 12 47 53

JPA에서 Team의 속한 Member들을 호출할 때 위와 같은 쿼리를 생성하여 가져오기 때문에 Team에서 Member로의 값을 세팅하지 않아도 제대로 출력은 됩니다. 하지만 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있습니다.


스크린샷 2021-08-26 오후 12 50 34

예를들어 위와 같이 em.flush(), em.clear()를 하지 않는다면 1차 캐시에 존재하는 값 그대로 가져오게 될 것입니다. 즉, JPA에서 외래키를 사용하여 쿼리를 생성하지 못하기 때문에 Team에서 Member의 값을 가져올 때는 아무 것도 가져오지 못하게 됩니다.



연관관계 편의 메소드

양쪽의 값을 저장해야 할 때 하나는 까먹을 가능성이 존재합니다. 그래서 두 코드를 하나인 것처럼 만드는 것이 안전한데요.

스크린샷 2021-08-26 오후 12 56 45

Member에서 Team을 저장할 때 위와 같이 한번에 Team에서 Member로 저장하는 코드도 같이 하나의 메소드에 존재한다면 개발자가 값을 한쪽에만 세팅하는 실수가 없어질 것입니다.



양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료를 해야 합니다.
  • 양방향 매핑은 반대 방향으로 조회기능이 추가된 것뿐입니다.
  • JPQL에서 역방향으로 탐색할 일이 많습니다.
  • 단방향 매핑을 잘해놓으면 필요할 때만 양방향 관계를 추가하면 됩니다.