Skip to content

Commit f7b9fc2

Browse files
committed
feat: Add Chapter05.md
1 parent a140d37 commit f7b9fc2

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

chapter05/정균/Chapter05.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
![스크린샷 2021-08-25 오후 11 36 28](https://user-images.githubusercontent.com/45676906/130810743-d19c180f-ac40-4fde-9c11-d4c30efeee47.png)
25+
26+
<br>
27+
28+
![스크린샷 2021-08-25 오후 11 36 35](https://user-images.githubusercontent.com/45676906/130810762-4b4928c8-160c-4e6e-8352-ae19bdb3638b.png)
29+
30+
Member 객체와 Team 객체를 매핑하면 위와 같이 할 수 있습니다.
31+
32+
<br>
33+
34+
![스크린샷 2021-08-25 오후 11 41 09](https://user-images.githubusercontent.com/45676906/130811608-5302a24e-ba43-4bfc-911a-6aeec6a68e21.png)
35+
36+
이 상태에서 Team과 Member로 코드를 작성해보면 위에서 볼 수 있듯이 뭔가 `객체지향과는 맞지 않은 부분`이 존재합니다.
37+
38+
<br>
39+
40+
![스크린샷 2021-08-25 오후 11 45 13](https://user-images.githubusercontent.com/45676906/130812772-c622bbcc-2b4a-4817-b103-8b0eb0361d36.png)
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+
![스크린샷 2021-08-25 오후 11 54 11](https://user-images.githubusercontent.com/45676906/130813940-533c2741-2536-4a30-98e6-f50f0b7cb066.png)
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+
![스크린샷 2021-08-25 오후 11 57 54](https://user-images.githubusercontent.com/45676906/130814660-1e4e3d96-bedb-440c-b39e-843f05e45261.png)
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+
![스크린샷 2021-08-26 오전 12 10 06](https://user-images.githubusercontent.com/45676906/130816660-85866eee-048b-41bb-a454-6ea2bac40528.png)
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+
![스크린샷 2021-08-26 오전 12 26 08](https://user-images.githubusercontent.com/45676906/130819312-d6654cb1-07e4-4366-bb08-56bfc1cf5644.png)
105+
106+
연관관계 주인은 `Many` 쪽에 주고, `One` 쪽에는 `읽기 전용`으로 두는 것이 좋습니다. `연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것입니다.`
107+
즉, 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 합니다.
108+
109+
정리하면, 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있습니다. 주인이 아닌 반대편은 읽기만 가능하고 외래키를 변경하지는 못합니다.
110+
111+
<br> <br>
112+
113+
## `양방향 매핑시 가장 많이 하는 실수`
114+
115+
![스크린샷 2021-08-26 오후 12 28 30](https://user-images.githubusercontent.com/45676906/130895949-aed04060-029e-4d80-944b-65cef37f3e3c.png)
116+
117+
위와 같이 Team을 통해서 Member를 추가했습니다. 이 상태로 실행을 해보겠습니다.
118+
119+
<br>
120+
121+
![스크린샷 2021-08-26 오후 12 30 37](https://user-images.githubusercontent.com/45676906/130896037-c9610761-a6d0-47c7-b1c5-e6686f8f1402.png)
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+
![스크린샷 2021-08-26 오후 12 34 22](https://user-images.githubusercontent.com/45676906/130896367-4ec82644-e212-4462-bbe0-b6be69a04dc7.png)
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+
![스크린샷 2021-08-26 오후 12 45 16](https://user-images.githubusercontent.com/45676906/130897241-3f7c40bc-1163-4a79-aa02-18222149f756.png)
152+
153+
위의 코드처럼 한쪽 방향에만 값을 넣어주고 Team에서 Member를 호출하면 값이 제대로 출력이 될까? 할 수 있지만 제대로 출력이 됩니다.
154+
155+
<br>
156+
157+
![스크린샷 2021-08-26 오후 12 47 53](https://user-images.githubusercontent.com/45676906/130897470-1a770944-2ca6-45c2-b46d-560ccf948cd3.png)
158+
159+
JPA에서 Team의 속한 Member들을 호출할 때 위와 같은 쿼리를 생성하여 가져오기 때문에 Team에서 Member로의 값을 세팅하지 않아도 제대로 출력은 됩니다. 하지만 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있습니다.
160+
161+
<br>
162+
163+
![스크린샷 2021-08-26 오후 12 50 34](https://user-images.githubusercontent.com/45676906/130897687-7c77ded1-644b-4924-825d-b8a8f92b0cb9.png)
164+
165+
예를들어 위와 같이 `em.flush()`, `em.clear()`를 하지 않는다면 1차 캐시에 존재하는 값 그대로 가져오게 될 것입니다. 즉, JPA에서 외래키를 사용하여 쿼리를 생성하지 못하기 때문에 Team에서 Member의 값을 가져올 때는 아무 것도 가져오지 못하게 됩니다.
166+
167+
<br> <br>
168+
169+
## `연관관계 편의 메소드`
170+
171+
양쪽의 값을 저장해야 할 때 하나는 까먹을 가능성이 존재합니다. 그래서 두 코드를 하나인 것처럼 만드는 것이 안전한데요.
172+
173+
![스크린샷 2021-08-26 오후 12 56 45](https://user-images.githubusercontent.com/45676906/130898185-96df3668-b179-47ca-9e54-de90144b6009.png)
174+
175+
Member에서 Team을 저장할 때 위와 같이 한번에 Team에서 Member로 저장하는 코드도 같이 하나의 메소드에 존재한다면 개발자가 값을 한쪽에만 세팅하는 실수가 없어질 것입니다.
176+
177+
<br> <br>
178+
179+
## `양방향 매핑 정리`
180+
181+
- `단방향 매핑만으로도 이미 연관관계 매핑은 완료`를 해야 합니다.
182+
- `양방향 매핑은 반대 방향으로 조회기능이 추가된 것뿐`입니다.
183+
- JPQL에서 역방향으로 탐색할 일이 많습니다.
184+
- 단방향 매핑을 잘해놓으면 필요할 때만 양방향 관계를 추가하면 됩니다.

0 commit comments

Comments
 (0)