|
| 1 | +# `5장: 연관관계 매핑 기초` |
| 2 | + |
| 3 | +객체 관계 매핑에서 가장 어려운 부분이 바로 객체 연관관계와 테이블 연관관계를 매핑하는 일입니다. `객체의 참조와 테이블의 외래 키를 매핑하는 것이 이 장의 목표`입니다. |
| 4 | + |
| 5 | +<br> |
| 6 | + |
| 7 | +## `단방향 연관관계` |
| 8 | + |
| 9 | +연관관계 중에서 `다대일(N:1)` 단방향 관계를 가장 먼저 이해해야 합니다. |
| 10 | + |
| 11 | +- 회원과 팀이 있습니다. |
| 12 | +- 회원은 하나의 팀에만 소속될 수 있습니다. |
| 13 | +- 회원과 팀은 다대일 관계입니다. |
| 14 | + |
| 15 | +<br> |
| 16 | + |
| 17 | +<img width="1104" alt="스크린샷 2021-08-25 오후 11 31 38" src="https://user-images.githubusercontent.com/45676906/130809896-679b90c3-0db8-4df7-b4a3-974dc7dda539.png"> |
| 18 | + |
| 19 | +위의 그림을 보면 `회원 객체와 팀 객체는 단방향 관계`입니다. 즉, Member -> Team의 조회는 가능하지만 Team -> Member를 접근하는 필드는 없습니다. |
| 20 | +반면에 테이블 연관관계는 `회원 테이블과 팀 테이블은 양방향 관계`입니다. 즉, 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고 반대로 팀과 회원도 조인할 수 있습니다. |
| 21 | + |
| 22 | +`테이블과 객체는 이러한 큰 간격이 존재합니다.` |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +<br> |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | +Member 객체와 Team 객체를 매핑하면 위와 같이 할 수 있습니다. |
| 31 | + |
| 32 | +<br> |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +이 상태에서 Team과 Member로 코드를 작성해보면 위에서 볼 수 있듯이 뭔가 `객체지향과는 맞지 않은 부분`이 존재합니다. |
| 37 | + |
| 38 | +<br> |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +뭔가 조회하는 과정에서도 `객체지향`스럽지 않고 난잡하다는 느낌을 받을 수 있습니다. |
| 43 | + |
| 44 | +<br> |
| 45 | + |
| 46 | +<img width="1132" alt="스크린샷 2021-08-25 오후 11 49 20" src="https://user-images.githubusercontent.com/45676906/130813048-a2d45c6d-00c9-43d0-8949-08435be6bde2.png"> |
| 47 | + |
| 48 | +그래서 객체지향스럽게 설계하기 위해서는 위와 같이 `Member` 객체가 teamId가 아니라 Team 객체를 가지도록 설계를 해보겠습니다. |
| 49 | + |
| 50 | +<br> |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | +- 멤버가 N이고 팀이 1이기 때문에 Member 입장에서는 `N:1`이기 때문에 `ManyToOne` 어노테이션을 사용합니다. |
| 55 | +- `@JoinColumn`은 어떤 컬럼을 기준으로 JOIN 할지를 명시하는 어노테이션입니다. |
| 56 | + |
| 57 | +<br> |
| 58 | + |
| 59 | +<img width="1169" alt="스크린샷 2021-08-25 오후 11 54 56" src="https://user-images.githubusercontent.com/45676906/130814031-944c2489-cb28-43a2-8f6e-91de2e705102.png"> |
| 60 | + |
| 61 | +이렇게 설계를 한 후에 위에서 작성했던 코드를 리팩터링 해보겠습니다. |
| 62 | + |
| 63 | +<br> |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +Member에 TeamId가 아니라 Team을 저장하니까 확실히 아까보다는 좀 더 객체지향스럽고 코드도 깔끔해진 것을 볼 수 있습니다. |
| 68 | + |
| 69 | +<br> <br> |
| 70 | + |
| 71 | +## `양방향 연관관계와 연관관계의 주인` |
| 72 | + |
| 73 | +<img width="1231" alt="스크린샷 2021-08-26 오전 12 04 25" src="https://user-images.githubusercontent.com/45676906/130815668-c921e731-f2cc-41af-97ab-5e2efd6e430e.png"> |
| 74 | + |
| 75 | +위에서 보면 SQL에서는 `외래키`를 통해서 양쪽에서 참조할 수 있지만, 객체는 한쪽에서만 참조가 가능하다는 차이점이 있었습니다. 즉, 위의 예시로 보면 `Member -> Team`으로는 참조할 수 있지만, `Team -> Member`로는 참조할 수 없습니다. 즉, 연관관계를 하나 더 만들어야 합니다. |
| 76 | + |
| 77 | +이렇게 양쪽해서 서로 참조하는 것을 `양방향 연관관계`라고 하는데요. 정확히 말하면 `양방향 관계가 아니라 서로 다른 단방향 관계 2개`입니다. |
| 78 | + |
| 79 | +<br> |
| 80 | + |
| 81 | + |
| 82 | + |
| 83 | +그래서 `Team`에서 `Member`를 참조하기 위해서 위와 같이 설정할 수 있습니다. 여기서 보아야 할 점은 `mappedBy` 라는 것인데요. 이것은 `연관관계의 주인`이라는 것을 알아야 합니다. |
| 84 | + |
| 85 | +<br> |
| 86 | + |
| 87 | +## `연관관계의 주인` |
| 88 | + |
| 89 | +위에서 말했던 것처럼 `테이블은 외리캐 하나로 두 테이블의 연관관계를 관리`합니다. 하지만 엔티티는 `단방향 2개가 있어야 양방향 관계`가 됩니다. 그리고 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 합니다. |
| 90 | +이것을 `연관관계의 주인`이라고 합니다. |
| 91 | + |
| 92 | +<br> <br> |
| 93 | + |
| 94 | +## `양방향 매핑 규칙` |
| 95 | + |
| 96 | +- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정 |
| 97 | +- `연관관계의 주인만이 외래 키를 관리합니다.` |
| 98 | +- `주인이 아닌 쪽은 읽기만 가능합니다.` |
| 99 | +- 주인은 mappedBy 속성을 사용하지 않습니다. |
| 100 | +- 주인이 아니면 mappedBy 속성으로 주인을 지정합니다. |
| 101 | + |
| 102 | +<br> |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | +연관관계 주인은 `Many` 쪽에 주고, `One` 쪽에는 `읽기 전용`으로 두는 것이 좋습니다. `연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것입니다.` |
| 107 | +즉, 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 합니다. |
| 108 | + |
| 109 | +정리하면, 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있습니다. 주인이 아닌 반대편은 읽기만 가능하고 외래키를 변경하지는 못합니다. |
| 110 | + |
| 111 | +<br> <br> |
| 112 | + |
| 113 | +## `양방향 매핑시 가장 많이 하는 실수` |
| 114 | + |
| 115 | + |
| 116 | + |
| 117 | +위와 같이 Team을 통해서 Member를 추가했습니다. 이 상태로 실행을 해보겠습니다. |
| 118 | + |
| 119 | +<br> |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +그러면 분명히 `INSERT` 쿼리도 2번이 실행된 것을 볼 수 있습니다. 그래서 DB에도 값이 잘 들어갔는지 확인해보겠습니다. |
| 124 | + |
| 125 | +<br> |
| 126 | + |
| 127 | +<img width="278" alt="스크린샷 2021-08-26 오후 12 31 42" src="https://user-images.githubusercontent.com/45676906/130896129-e087668f-3e2d-49f1-a08f-04a4a5c93c42.png"> |
| 128 | + |
| 129 | +확인해보니 `Member 테이블에 TEAM_ID가 null인 것`을 볼 수 있습니다. 위에서 분명히 INSERT 쿼리도 실행이 되었고 값을 넣어주었던 거 같은데 말이죠.. |
| 130 | + |
| 131 | +왜 이런가 생각해보면 Member가 연관관계의 주인이고, Team은 MappedBy가 지정된 `읽기 전용`이기 때문에 Team을 통해서 Member를 저장하려 할 때는 제대로 저장이 되지 않는 것입니다. 그래서 이런 문제를 해결하려면 아래와 같이 수정하면 됩니다. |
| 132 | + |
| 133 | +<br> |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | +그래서 위와 같이 연관관계의 주인인 Member를 통해서 Team을 저장하도록 하면 제대로 값이 잘 들어가게 됩니다. |
| 138 | + |
| 139 | +<br> |
| 140 | + |
| 141 | +<img width="269" alt="스크린샷 2021-08-26 오후 12 35 44" src="https://user-images.githubusercontent.com/45676906/130896436-cd6c5697-ec81-43d3-b632-d45525849fca.png"> |
| 142 | + |
| 143 | +<br> <br> |
| 144 | + |
| 145 | +## `순수한 객체까지 고려한 양방향 연관관계` |
| 146 | + |
| 147 | +그렇다면 정말 연관관계의 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않아도 될까요? 객체 관점에서는 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전합니다. |
| 148 | + |
| 149 | +<br> |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | +위의 코드처럼 한쪽 방향에만 값을 넣어주고 Team에서 Member를 호출하면 값이 제대로 출력이 될까? 할 수 있지만 제대로 출력이 됩니다. |
| 154 | + |
| 155 | +<br> |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | +JPA에서 Team의 속한 Member들을 호출할 때 위와 같은 쿼리를 생성하여 가져오기 때문에 Team에서 Member로의 값을 세팅하지 않아도 제대로 출력은 됩니다. 하지만 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있습니다. |
| 160 | + |
| 161 | +<br> |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +예를들어 위와 같이 `em.flush()`, `em.clear()`를 하지 않는다면 1차 캐시에 존재하는 값 그대로 가져오게 될 것입니다. 즉, JPA에서 외래키를 사용하여 쿼리를 생성하지 못하기 때문에 Team에서 Member의 값을 가져올 때는 아무 것도 가져오지 못하게 됩니다. |
| 166 | + |
| 167 | +<br> <br> |
| 168 | + |
| 169 | +## `연관관계 편의 메소드` |
| 170 | + |
| 171 | +양쪽의 값을 저장해야 할 때 하나는 까먹을 가능성이 존재합니다. 그래서 두 코드를 하나인 것처럼 만드는 것이 안전한데요. |
| 172 | + |
| 173 | + |
| 174 | + |
| 175 | +Member에서 Team을 저장할 때 위와 같이 한번에 Team에서 Member로 저장하는 코드도 같이 하나의 메소드에 존재한다면 개발자가 값을 한쪽에만 세팅하는 실수가 없어질 것입니다. |
| 176 | + |
| 177 | +<br> <br> |
| 178 | + |
| 179 | +## `양방향 매핑 정리` |
| 180 | + |
| 181 | +- `단방향 매핑만으로도 이미 연관관계 매핑은 완료`를 해야 합니다. |
| 182 | +- `양방향 매핑은 반대 방향으로 조회기능이 추가된 것뿐`입니다. |
| 183 | +- JPQL에서 역방향으로 탐색할 일이 많습니다. |
| 184 | +- 단방향 매핑을 잘해놓으면 필요할 때만 양방향 관계를 추가하면 됩니다. |
0 commit comments