๐ ๋๊ท๋ชจ ์์คํ ์๋ฒ ์ธํ๋ผ ๊ธฐ์ด
- Scale Up (์์ง์ ํ์ฅ) : ํ๋์จ์ด ์ฑ๋ฅ ํฅ์ (ex. RAM 16 → 64GB)
- Scale Out (์ํ์ ํ์ฅ) : ์๋ฒ ์ฌ๋ฌ๋, ๋ก๋ ๋ฐธ๋ฐ์๋ก ์์ฒญ ๋ถ์ฐ
- Load Balancer : ํธ๋ํฝ์ ๋ผ์ฐํ ๋ฐ ๋ถ์ฐํ๊ธฐ ์ํ ๋๊ตฌ (Client๋ Load Balancer ๋ก ์์ฒญ์ ๋ณด๋ด๋ฉด , Load Balancer๋ ์์ฒญ์ ์ ์ ํ ๋ถ์ฐํ์ฌ ์๋ฒ๋ก ์ ๋ฌ)
- Cache : ์๋ฒ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์ด ๊ธธ์ด์ง ์๋ก ์๋ต์ ๋๋ ค์ง๋ค. ๊ทธ๋์ ์์ฃผ ์์ฒญ๋๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฏธ๋ฆฌ ์ ์ฅํด๋๊ณ , ๋ค์์ ๋ ์์ฒญ์ด ์ค๋ฉด ๋น ๋ฅด๊ฒ ์๋ตํด์ฃผ๋ ์ ์ฅ์๋ฅผ ๋๋ค.
๐ ์บ์ ์ข ๋ฅ
๋ธ๋ผ์ฐ์ ์บ์ (ํ๋ก ํธ)
์๋ฒ ์บ์ (Spring์์ @Cacheable)
Redis๊ฐ์ ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ ์๋ฒ
CDN ์บ์ (์ ์ ๋ฆฌ์์ค, ์ด๋ฏธ์ง, js ๋ฑ)
- ์ค๋ฉ (Sharding) : ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ์๋ฒ(๋๋ ํํฐ์
)๋ก ๋๋์ด ์ ์ฅํ๋ ๋ฐฉ๋ฒ (shard ๋ค : 1~1,000 ์๋ฒA, 1,001~2,000 ์๋ฒB)
MongoDB, Cassandra ๊ฐ์ NoSQL DB์์ ์์ฃผ ์ฌ์ฉ - ๋ ํ๋ฆฌ์ผ์ด์ (Replication) : ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ์๋ฒ์ ๋ณต์ ํด ์ ์ฅํ๋ ๋ฐฉ๋ฒ. ์ฃผ๋ก ์ฝ๊ธฐ ์ฑ๋ฅ์ ๋์ด๊ฑฐ๋ ์ฅ์ ๋ณต๊ตฌ(๊ณ ๊ฐ์ฉ์ฑ)๋ฅผ ์ํด ์ฌ์ฉ๋จ.
- ๊ณ ๊ฐ์ฉ์ฑ : ํ๋ ๋ง๊ฐ์ ธ๋ ๋ค๋ฅธ ์ ๊ฐ ๊ทธ ์ญํ
- ๋ธ๋ผ์ฐ์ ์บ์
- DNS : Domain Name System → ๋๋ฉ์ธ ์ด๋ฆ(์: google.com)์ IP ์ฃผ์(์: 142.250.206.14)๋ก ๋ฐ๊ฟ์ฃผ๋ ์์คํ
DNS๊ฐ ๋๋ฆฌ๋ฉด → ์น์ฌ์ดํธ ์ ์ ์๋๋ ๋ฆ์ด์ง
๊ทธ๋์ OS๋ ๋ธ๋ผ์ฐ์ ์์ ์ต๊ทผ ์ ์ํ ๋๋ฉ์ธ์ ์บ์ฑํ๋ค.
์ฑ๋ฅ ํ๋ ์์๋ nslookup, dig, ping ์ผ๋ก DNS ํ์ธ ์์ฃผ ํจ. - DNS ์ฟผ๋ฆฌ : ๋๋ฉ์ธ ์ด๋ฆ์ ๋ํ IP ์ฃผ์๊ฐ ๋ญ์ผ?"๋ผ๊ณ DNS ์๋ฒ์ ์ง๋ฌธ(์์ฒญ)์ ๋ณด๋ด๋ ๊ฒ
- ๋ถ์ฐ๋ ๊ฐ ์๋ฒ์ ๋ํ Client ๋ผ์ฐํ
์ ์ํด DNS ํ์ฉ? :
์ฌ์ฉ์ ์์ฒญ์ด ์์ ๋ ํ๊ตญ ์ฌ๋์ ์์ธ ์๋ฒ๋ก ๋ณด๋ด๋๊ฒ ๋น ๋ฅด๊ณ , ๋ฏธ๊ตญ ์ฌ๋์ ๋ฏธ๊ตญ ์๋ฒ๋ก ๋ณด๋ด๋๊ฒ ๋น ๋ฅด๋ค.
→ ์ด๊ฒ์ ๋ฐ๋ก Client ๋ผ์ฐํ
→ ์ฆ, ๋ค์คํ ๋ ์๋ฒ ์ค ์ด๋ ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ผ์ง ๊ฒฐ์ ํ๋ ๊ฒ.
* ๋ผ์ฐํ ๋ฐฉ์์ ๋ค์ํ๊ณ ๊ทธ ์ค 1. DNS 2. GSLB๊ฐ ์๋ค.
1. DNS : ๋๋ฉ์ธ → IP์ฃผ์๋ฅผ ๋งคํํด์ค๋ค. ์๋์ฒ๋ผ ์ค์ ๊ฐ๋ฅ
ํ๊ตญ์์ example.com → ์์ธ ์๋ฒ IP
๋ฏธ๊ตญ์์ example.com → ๋ฏธ๊ตญ ์๋ฒ IP
์ด๋ฅผ GeoDNS ๋๋ DNS ๊ธฐ๋ฐ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ด๋ผ๊ณ ํ๋ค. - ์ ๋ฐํ๊ณ ๋ณต์กํ ๋ผ์ฐํ
์ด ํ์ํ ๊ฒฝ์ฐ GSLB ๋ฑ ๋ค์ํ ๋ฐฉ๋ฒ ํ์ฉ? : Global Server Load Balancing
GSLB๋ ์ข ๋ ๋๋ํ ๋ผ์ฐํ ์์คํ . DNS์ฒ๋ผ IP๋ฅผ ๋ฐํํด์ฃผ์ง๋ง
'์ฌ์ฉ์์ ์์น', '์๋ฒ ์ํ (์ฃฝ์๋์ง ์ด์๋์ง)', '์๋ต ์๋', 'ํธ๋ํฝ ๋ถํ์ ๋' ์ ๊ฐ์ ๋ค์ํ ์กฐ๊ฑด์ ๊ณ ๋ คํด์ ๊ฐ์ฅ ์ ์ ํ ์๋ฒ๋ฅผ ๊ณจ๋ผ์ค๋ค. - ๋จ์ผ ์ ํ๋ฆฌ์ผ์ด์
์ ์ปค์ง์๋ก ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง ์ ์๋ค. :
๋จ์ผ ์๋ฒ๊ฐ ์ฒ๋ฆฌํด์ผํ ํธ๋ํฝ์ด ๋๋ฌด ๋ง์์ง๋ฉด, ๋ฆฌ์์ค๊ฐ ๋ถ์กฑํ ์๋ ์๊ณ , ์์คํ ์ด ์ปค์ง์๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์์ง ์๋ ์๊ธฐ ๋๋ฌธ
- ๋จ์ผ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ธฐ๋ฅ์ ๋ง๋ ์ฌ๋ฌ๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ ์ ์๋ค. :
๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ด๋นํ ๊ธฐ๋ฅ์ ๋ํด ๊ฐ๋ณ ์์ ๋ง ์ฒ๋ฆฌํ ์ ์๋ ๊ฒ์ด๋ค.
์๋ฅผ ๋ค๋ฉด, A์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒ์๊ธ ๊ธฐ๋ฅ๋ง, B์ ํ๋ฆฌ์ผ์ด์ ์ ๋๊ธ ๊ธฐ๋ฅ๋ง ์ฒ๋ฆฌํ๋ค.
ํ์ํ๋ค๋ฉด, DB๋ ๊ฐ ๊ธฐ๋ฅ ๋ณ๋ก ๋ถ๋ฆฌํ์ฌ ๊ตฌ์ฑํ ์ ์๋ค.
- ๋ถ๋ฆฌ๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์ด ํ๋์ ์๋น์ค๋ฅผ ์ด๋ฃจ๋ ค๋ฉด, ์๋ก ๋คํธ์ํฌ ํต์ ์ด ํ์ํ๋ค. :
API๋ฅผ ํตํด ์ง์ ์ ์ผ๋ก ํต์ ํ ์๋ ์์ง๋ง, ๋ฉ์ธ์ง(์ด๋ฒคํธ)๋ฅผ ํตํด ๊ฐ์ ์ ์ผ๋ก ํต์ ํ ์๋ ์๋ค.
๊ฐ ๋ฐฉ์์ด ๋ฌด์์ด๊ณ ์ฅ๋จ์ ์ ๋ฌด์์ผ์ง, ์ด๋ป๊ฒ ๊ฐ๋ฐํ ์ ์์์ง๋ ์ฐจ๊ทผ์ฐจ๊ทผ ์์๋ณด์

- ์์คํ
์ํคํ
์ณ๋? :
์์คํ ์ ๊ตฌ์กฐ๋ ์ค๊ณ ๋ฐฉ์ (์ํํธ์จ์ด ์์คํ ์ด ์ด๋ป๊ฒ ๊ตฌ์ฑ๋๊ณ ๋์ํ ์ง์ ๋ํ '์ค๊ณ ๊ตฌ์กฐ๋')
์ฝ๊ฒ ๋งํ๋ฉด: ์ด ์์คํ , ์ด๋ค ๋ถํ๋ค๋ก ์ด๋ฃจ์ด์ ธ ์๊ณ , ๊ทธ ๋ถํ๋ค์ด ์ด๋ป๊ฒ ์๋ก ์ฐ๊ฒฐ๋ผ ์๋์ง"์ ๋ํ ์ฒญ์ฌ์ง
ํ์ฅ์ฑ, ์ ์ง๋ณด์์ฑ, ์ฑ๋ฅ ๋ฑ ํฐ ์ํฅ
๐ ๋ํ์ ์ธ ์ํคํ ์ฒ
Monoloithic Architecture vs Microservice Architecture - Monoloithic Architecture

๋ชจ๋ ๊ธฐ๋ฅ์ด ํ๋์ ํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ด๊ฒจ ์์
- ๊ตฌ์กฐ: ํ๋์ ํ๋ก์ ํธ๋ก ๋ชจ๋ ๊ธฐ๋ฅ ์ฒ๋ฆฌ
- ์ฅ์ : ๋จ์ํ๊ณ ๋น ๋ฅด๊ฒ ๊ฐ๋ฐ ๊ฐ๋ฅ
- ๋จ์ : ๊ธฐ๋ฅ์ด ๋ง์์ง๋ฉด ์ ์ง๋ณด์ ์ด๋ ค์
→ ex) ํ์๊ฐ์ , ๊ฒ์ํ, ๋๊ธ, ๊ฒฐ์ ๊ธฐ๋ฅ → ๋ชจ๋ ํ๋์ Spring Boot ์ฑ์ ์์

๋ถํ ๊ฐ๋น์ ์ํด Scale-Up (๋จ์ผ ์๋ฒ์ ์ฑ๋ฅ ํฅ์ : ์๋ฒ๋ฅผ ๋ฌดํ์ Scale-Upํ๋ ๊ฒ์ ํ๊ณ๊ฐ ์๊ณ ๋น์ฉ๋ ๋งค์ฐ ๋น์ธ๋ค.)์ ๊ณ ๋ คํด ๋ณผ ์ ์๋ค.

๊ทธ ๋ค์์ Scale-Out (์ํ์ ํ์ฅ : ์๋ฒ ์ฌ๋ฌ๋์ ๋ถํ ๋ถ์ฐ)์ ๊ณ ๋ คํด ๋ณผ ์ ์๋ค.
Scale-Out ๊ตฌ์ฑ์ ๋ณด๋ฉด ๋ฆฌ์์ค๊ฐ ๋ญ๋น๋๋ ์ง์ ์ด ์๋ค. (๋ถ๋ช
๊ฒ์๊ธ ๊ธฐ๋ฅ์ ๋ํ ๋ถ๋ด๋ง ๋์ํด์ฃผ๋ฉด ์ถฉ๋ถํ์ ๊ฒ์ด๋ค.)
Monolithic Architecture์์๋ ๋ชจ๋ ๊ธฐ๋ฅ์ด ๋จ์ผ ์ ํ๋ฆฌ์ผ์ด์
์ ํจ๊ป ๋ฐฐํฌ๋๋ฏ๋ก, ๊ฒ์๊ธ๊ณผ ๊ด๋ จ ์๋ ๋ค๋ฅธ ๊ธฐ๋ฅ๋ค๋ ๊ฐ์ด ๋ฐฐํฌ๋์ด์ ๋ฆฌ์์ค๋ฅผ ์ ์ ํ๊ณ ์๋ ๊ฒ์ด๋ค.
- Microservice Architecture
์์คํ ์ด ์๊ณ ๋ ๋ฆฝ์ ์ธ ์๋น์ค(Microservice)๋ก ๊ตฌ์ฑ
๊ฐ ์๋น์ค๋ ๋จ์ผ ๊ธฐ๋ฅ์ ๋ด๋นํ๋ฉฐ, ๋ ๋ฆฝ์ ๋ฐฐํฌ ๊ฐ๋ฅ
์ฐ๋ฆฌ์ ์์คํ ์์๋,
๊ฒ์๊ธ, ๋๊ธ, ์ข์์, ์กฐํ์, ์ธ๊ธฐ๊ธ, ๊ฒ์๊ธ ์กฐํ
๊ฐ ์๊ตฌ์ฌํญ(๊ธฐ๋ฅ)์ ๋ ๋ฆฝ๋ ๋ง์ดํฌ๋ก์๋น์ค๋ก ๋ณผ ์๋ ์๋ค.
์๋น์ค ๋จ์๋ก ์ ์ฐํ ํ์ฅ ๊ฐ๋ฅ
ํ์ง๋ง, ์๋น์ค ๊ฐ ๋ณต์กํ ํต์ ๋ฐ ๋ชจ๋ํฐ๋ง ํ์
๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ฐ ํธ๋์ญ์ ๊ด๋ฆฌ์ ์ด๋ ค์
์ด๋ ค์ด ๊ฐ๋ฐ ๋์ด๋ - Docker
Docker๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ปจํ ์ด๋(container)๋ผ๋ ๋จ์๋ก ํฌ์ฅํด์, ์ด๋์๋ ์คํํ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ์
: ์ด? ๋ด ์ปดํจํฐ์์๋ ๋๋๋ฐ? ๊ทผ๋ฐ ๋ ์ปดํจํฐ๋ ๋๋ ํ๊ฒฝ์ด ๋ค๋ฅด์์.. (OS, ์ค์ , ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฒ์ ๋ฑ๋ฑ)
๊ทธ๋์ ๊ฐ๋ฐํ๊ฒฝ๊ณผ ์คํํ๊ฒฝ์ ๋๊ฐ์ด ๋ง๋ค ์ ์๋ ๋ฐฉ๋ฒ์ด ํ์ํ๋ค.
→ ์ปจํ ์ด๋ ์์ ์ฑ๊ณผ ์คํํ๊ฒฝ์ ํต์งธ๋ก ๋ด์์ ๋ฐฐํฌ
๐ ๊ธฐ๋ฅ
์ปจํ ์ด๋ ์คํ | ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๊ทธ ํ๊ฒฝ์ ๋ถ๋ฆฌ๋ ๊ณต๊ฐ์์ ์คํ |
์ด๋ฏธ์ง ์์ฑ | ์ ํ๋ฆฌ์ผ์ด์ ์คํ์ ํ์ํ ๋ชจ๋ ๊ฒ(OS, ์ฝ๋ ๋ฑ)์ ์ด๋ฏธ์ง๋ก ํจํค์ง |
์ด์์ฑ ๋ณด์ฅ | ์ด๋์์๋ ๋์ผํ๊ฒ ์คํ ๊ฐ๋ฅ (๋ฆฌ๋ ์ค/์๋์ฐ/Mac/ํด๋ผ์ฐ๋ ๋ฑ) |
๊ฒฝ๋ ์คํ | ๊ฐ์๋จธ์ ๋ณด๋ค ๊ฐ๋ณ๊ณ ๋น ๋ฆ |
๐ ํธ๋ฆฌํ ์
โ๏ธ ์ด๋์๋ ๋์ผํ๊ฒ ์คํ | ํ๊ฒฝ์ด ๋ฌ๋ผ๋ Docker ์ปจํ ์ด๋ ์์์ ๋์ผ |
๐ ๏ธ ๋น ๋ฅธ ๊ฐ๋ฐ ๋ฐ ํ ์คํธ | ๊ฐ๋ฐ์๊ฐ Dockerfile๋ก ์คํ ํ๊ฒฝ์ ํต์ผ |
๐ ๋ฐฐํฌ ์๋ํ์ CI/CD ์ฉ์ด | ๋น๋/๋ฐฐํฌ ํ์ดํ๋ผ์ธ์ ์ฝ๊ฒ ํตํฉ๋จ |
๐ก ๊ฐ์๋จธ์ ๋ณด๋ค ๊ฐ๋ณ๊ณ ๋น ๋ฆ | OS ์ ์ฒด๊ฐ ์๋๋ผ, ์ฑ ์์ค๋ง ๊ฒฉ๋ฆฌ |
- Redis
๐ 1. ๊ฐ๋
Redis๋ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ ์ ์ฅ์
์ ํํ๋ Key-Value ํํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ด์, ์บ์ ์๋ฒ์ด์, ๋ฉ์์ง ๋ธ๋ก์ปค ์ญํ ๋ ๊ฐ๋ฅ.
๐ 2. ๋ฑ์ฅ ๋ฐฐ๊ฒฝ
๊ธฐ์กด RDB๋ ํ์ผ ๊ธฐ๋ฐ DB๋ ์๋๊ฐ ๋๋ ค์ ๊ณ ์์ ์ฝ๊ธฐ/์ฐ๊ธฐ ์ฑ๋ฅ์ด ํ์ํ ์ํฉ์์๋ ์ ํฉํ์ง ์์์.
→ ๊ทธ๋์ ๋ฉ๋ชจ๋ฆฌ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด์ ๋น ๋ฅด๊ฒ ์ฝ๊ณ ์ฐ๋ ์ ์ฅ์๊ฐ ํ์ํ๋ค.
→ Redis๋ ์ด๋ฅผ ์ํด ๋ฑ์ฅ
๐ 3. ๊ธฐ๋ฅ
Key-Value ์ ์ฅ | ๋ฌธ์์ด, ๋ฆฌ์คํธ, ํด์, ์ ๋ฑ ๋ค์ํ ๊ตฌ์กฐ ์ง์ |
TTL(๋ง๋ฃ์๊ฐ) | ์๋ ์บ์ ๋ง๋ฃ ๊ธฐ๋ฅ |
Pub/Sub | ๋ฉ์์ง ๋ฐํ/๊ตฌ๋ ๊ธฐ๋ฅ |
Atomic ์ฐ์ฐ | ๋์์ฑ ๋ฌธ์ ์์ด ์์ ํ ์ฐ์ฐ ๊ฐ๋ฅ |
Persistence ์ต์ | ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ๋์ง๋ง ๋์คํฌ์๋ ์ ์ฅ ๊ฐ๋ฅ (RDB, AOF ๋ฐฉ์) |
- Kafka
๐ 1. ๊ฐ๋
Kafka๋ ๋ถ์ฐํ ๋ฉ์์ง ์คํธ๋ฆฌ๋ฐ ํ๋ซํผ.
์ฝ๊ฒ ๋งํ๋ฉด, ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ๋ฌํ๊ณ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ณ ์ฑ๋ฅ ๋ฉ์์ง ํ ์์คํ .
๐ 2. ๋ฑ์ฅ ๋ฐฐ๊ฒฝ
๊ธฐ์กด ๋ฉ์์ง ํ ์์คํ (RabbitMQ ๋ฑ)์ ๋ค์๊ณผ ๊ฐ์ ํ๊ณ๋ฅผ ๊ฐ์ก์:
- ๋์ฉ๋ ์ฒ๋ฆฌ์ ์ด๋ ค์
- ๋ฐ์ดํฐ ์์ค ๊ฐ๋ฅ์ฑ
- ๋๋ฆฐ ์๋น์ ์ฒ๋ฆฌ
→ LinkedIn์ด ๋น ๋ฐ์ดํฐ ์ค์๊ฐ ์ฒ๋ฆฌ ์์คํ ์ ์ํด Kafka๋ฅผ ๋ง๋ค์๊ณ , ์ง๊ธ์ ๊ฑฐ์ ํ์ค ์ค์๊ฐ ๋ฉ์์ง ํ๋ซํผ์ด ๋๋ค.
๐ 3. ํต์ฌ ๊ธฐ๋ฅ
Publisher → Broker → Subscriber ๊ตฌ์กฐ | ๋ฐ์ดํฐ ๋ฐํ → Kafka์ ์ ์ฅ → ๊ตฌ๋ ์๊ฐ ์ฝ์ |
Topic ๊ธฐ๋ฐ ๋ฉ์์ง ํ | ๋ฐ์ดํฐ๋ Topic์ด๋ผ๋ ๋จ์๋ก ๋๋ ๊ด๋ฆฌ |
๋ด๊ตฌ์ฑ ๋์ ๋ก๊ทธ ์ ์ฅ | ๋์คํฌ์ ๋ก๊ทธ๋ฅผ ์ ์ฅํด์ ์์ค ์์ด ์ ๋ฌ |
๋ถ์ฐ ์ฒ๋ฆฌ | ์ฌ๋ฌ ๋ธ๋ก์ปค๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋๋ ์ ์ ์ฅํ๊ณ ์ฒ๋ฆฌ |
Consumer ๊ทธ๋ฃน | ์ฌ๋ฌ Consumer๊ฐ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ |
- ์ธ๋ฉ๋ชจ๋ฆฌ ์ ์ฅ์๋?
๋ฐ์ดํฐ๋ฅผ ํ๋๋์คํฌ ๊ฐ์ ์ ์ฅ์ฅ์น๊ฐ ์๋๋ผ, RAM(๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ)์ ์ ์ฅํ๋ ์ ์ฅ์
์ RAM(๋ฉ๋ชจ๋ฆฌ)์ ์ ์ฅํ๋?
RAM์ ๋น ๋ฅด๊ณ ํ๋๋ ๋๋ฆฌ๊ธฐ ๋๋ฌธ.
→ ๊ทธ๋์ ์๋๊ฐ ์ค์ํ ์์คํ ์์๋ ๋ฐ์ดํฐ๋ฅผ RAM์ ์ ์ฅํด์ ๋น ๋ฅด๊ฒ ์ฝ๊ณ ์ฐ๋ ๋ฐฉ์์ ์ด๋ค.
์ด๊ฒ ๋ฐ๋ก ์ธ๋ฉ๋ชจ๋ฆฌ ์ ์ฅ์
Redis ๊ฐ์ ์ธ๋ฉ๋ชจ๋ฆฌ ์ ์ฅ์๋ ์๋ฒ์์ ์คํ๋๋ ๋ณ๋ ํ๋ก์ธ์ค.
์ด ํ๋ก์ธ์ค๋ ์๋ฒ์ ๋ฉ๋ชจ๋ฆฌ(RAM)์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ ค์ ๋ณด๊ด.
์ฆ, "๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆฐ๋ค"๋ ๊ฑด ๋์คํฌ๊ฐ ์๋ RAM์์ ๋ฐ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฝ๋๋ค๋ ๋ป.
๐ ๊ทธ๋์ ์๋๊ฐ ์์ฒญ ๋น ๋ฅธ ๋์ , ์๋ฒ๊ฐ ๊บผ์ง๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ด์ฉ์ ๋ ์๊ฐ๋ค.
→ ํ์ง๋ง Redis๋ ๋์คํฌ์ ๋ฐฑ์ ์ ๋จ๊ฒจ์ ๋ณต๊ตฌ ๊ฐ๋ฅํ๊ฒ๋ ํ ์ ์์ - RDB vs Redis ๊ตฌ์กฐ ์ฐจ์ด
๐๏ธ RDB (Relational Database, ์: MySQL, MariaDB)
๊ตฌ์กฐ | ํ ์ด๋ธ ๊ธฐ๋ฐ, ํ-์ด ๊ตฌ์กฐ |
์ฟผ๋ฆฌ ๋ฐฉ์ | SQL ์ฌ์ฉ (SELECT * FROM user WHERE id = 1) |
๋ชฉ์ | ์ ํํ๋ ๋ฐ์ดํฐ๋ฅผ ์๊ตฌ ์ ์ฅ |
์์ | ์ ์ ํ ์ด๋ธ, ๊ฒ์๊ธ ํ ์ด๋ธ ๋ฑ |
โก Redis (In-memory Key-Value Store)
๊ตฌ์กฐ | Key → Value ๊ตฌ์กฐ (์: "session:user123" → "{username: 'kim'}") |
์ฟผ๋ฆฌ ๋ฐฉ์ | ๋ช ๋ น์ด ๊ธฐ๋ฐ (GET key, SET key value) |
๋ชฉ์ | ๋น ๋ฅธ ์ฝ๊ธฐ/์ฐ๊ธฐ, ์บ์ฑ, ์ธ์ ๋ฑ |
- ๋ฉ์์ง ๋ธ๋ก์ปค๋?
ํ ์์คํ ์์ ๋ค๋ฅธ ์์คํ ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ค๊ฐ์์ ์ ๋ฌํด์ฃผ๋ ์ค๊ณ์ ์ญํ
๋ํ์ ์ธ ๋ฉ์์ง ๋ธ๋ก์ปค: Kafka, RabbitMQ, Redis (๊ฒฝ๋์ฉ)
๐จ Redis์ Pub/Sub ๊ธฐ๋ฅ์ด๋?
Publisher๊ฐ ์ด๋ค ์ฑ๋์ ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ฉด, Subscriber๊ฐ ๊ทธ ์ฑ๋์ ๊ตฌ๋ ํ๊ณ ์๋ค๊ฐ ๋ฉ์์ง๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ฐ์
# A ์์คํ
(Publisher)
PUBLISH news "์ ๊ฒ์๊ธ ์ฌ๋ผ์์ต๋๋ค!"
# B ์์คํ
(Subscriber)
SUBSCRIBE news
→ "์ ๊ฒ์๊ธ ์ฌ๋ผ์์ต๋๋ค!" ์์
โ Redis๊ฐ ๋ฉ์์ง ๋ธ๋ก์ปค๋ก ์ฐ์ผ ๋์ ํน์ง
์ฅ์ | ๋น ๋ฅด๊ณ ์ค์ ์ฌ์, ์ค์๊ฐ ์๋ฆผ์ ์ ํฉ |
๋จ์ | ๋ฉ์์ง ๋๋ฝ ๊ฐ๋ฅ (์ง์ ์ ์ฅ ์ ํจ), ์์์ฑ ์์ |
์ฌ์ฉ ์ | ์ค์๊ฐ ์ฑํ , ์๋ฆผ, ๊ฒฝ๋ ์ด๋ฒคํธ ์ ๋ฌ |
- ๋ด๊ฐ ๋ฃ๊ณ ์๋ ๊ฐ์์์ MySQL, Redis, Kakfa๋ฅผ ์ค์นํ๊ธฐ ์ํด Docker๋ฅผ ์ฌ์ฉํ ๊ฑฐ ๋ผ๋๋ฐ ๋ฌด์จ ์๋ฏธ?
→ ๊ทธ๋ฌ๋๊น ๋ด ํธ์คํธ PC์ ์์ ํ ๋ถ๋ฆฌ๋ ์๋ก์ด ๊ฐ์ํ๊ฒฝ์์ ์ธํ ํ๊ฒ ๋ค๋๊ฑฐ์ง? ๊ทธ๋ฌ๋๊น ๋ด ๊ธฐ์กด ์ค์ ๊ณผ ์ถฉ๋๋ ์๊ณ - docker์์ ํ๋ก๊ทธ๋จ์ ์ด๋ฏธ์ง๋ก ํจํค์ง๋๊ณ , ์ปจํ
์ด๋๋ก ์คํ๋๋ค๋๋ฐ ๋ฌด์จ ์๋ฏธ์ง?
ํ๋ก๊ทธ๋จ-> ์ด๋ฏธ์ง ๊ณผ ํ๋ก์ธ์ค -> ์ปจํ ์ด๋๋ก ์ดํดํ๋ฉด ๋๊ฒ ๋ค ๊ทธ์ง? - ๋ฉํฐ ๋ชจ๋ ํ๋ก์ ํธ ๊ตฌ์ฑ์ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์ธํ
ํ๋๋ฐ ์๋ฏธ๋ค์ ์ ๋ชจ๋ฅด๊ฒ ๋ค.
์๋ฏธ๋ฅผ ์ข ์ค๋ช ์ ์ข ํด์ค
service/article/build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'kuke'
version = '1'
allprojects {
repositories {
mavenCentral()
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
}
settings.gradle
rootProject.name = 'kuke-board'
include 'common'
include 'service'
include 'service:article'
- ์์ง ์ค๋ฉ & ์ํ ์ค๋ฉ
์์ง ์ค๋ฉ (Vertical Sharding) : ๋ฐ์ดํฐ๋ฅผ ์์ง์ผ๋ก ๋ถํ ํ๋ ๋ฐฉ์ (์ปฌ๋ผ ๋จ์) : PK ์๋ณ์๋ฅผ ํ์ฉํด์ ์ข์ธก์ค๋, ์ฐ์ธก์ค๋ ๋ถ์ฐ
์ํ ์ค๋ฉ (Horizontal Sharding) : ๋ฐ์ดํฐ๋ฅผ ์ํ์ผ๋ก ๋ถํ ํ๋ ๋ฐฉ์ (ํ ๋จ์) : 1~5,000, 5,001~10,000 - 2PC (2-Phase Commit)
๋ถ์ฐ ํธ๋์ญ์ ์์ ์ฌ๋ฌ DB(์ค๋)์ ์ผ๊ด๋ ์ปค๋ฐ/๋กค๋ฐฑ์ ๋ณด์ฅํ๊ธฐ ์ํ ํ๋กํ ์ฝ.
Phase 1 : Prepare
Coordinator๊ฐ ๊ฐ ์ฐธ์ฌ DB์๊ฒ "์ด๊ฑฐ ์ปค๋ฐํด๋ ๋๋?"๋ผ๊ณ ๋ฌผ์ด๋ด.
๊ฐ DB๋ "OK, ์ค๋น๋จ" or "NO, ๋ฌธ์ ์์" ์๋ต.
Phase 2 : Commit or Rollback
๋ชจ๋ OK๋ฉด → ๋ชจ๋ DB์ Commit
ํ๋๋ผ๋ NO๋ฉด → ๋ชจ๋ DB์ Rollback
๊ตฌ๋ถ | ์์ง ์ค๋ฉ (Vertical) | ์ํ ์ค๋ฉ (Horizontal) |
๊ฐ๋ | ์ปฌ๋ผ(์์ฑ) ๋จ์ ๋ถ๋ฆฌ ex) article ํ ์ด๋ธ์ ์ ๋ฐ์ A DB์, ์ ๋ฐ์ B DB์ ์ ์ฅ |
ํ(row) ๋จ์ ๋ถ๋ฆฌ ex) article_id 1 |
ํธ๋์ญ์ ๋ณต์ก๋ | ๋์ ํ๋์ ๋ ผ๋ฆฌ ํ ์ด๋ธ์ด ์ฌ๋ฌ DB์ ๊ฑธ์ณ ์์ → ์กฐ์ธ, ํธ๋์ญ์ ๋งค๋ฒ ๋ถ์ฐ๋จ |
์๋์ ์ผ๋ก ๋ฎ์ ํ๋์ ๋ก์ฐ๋ ํ๋์ ์ค๋์๋ง ์กด์ฌํ๋ฏ๋ก, ๋๋ถ๋ถ ๋จ์ผ DB ํธ๋์ญ์ |
์ฅ์ | ์ปฌ๋ผ ๋จ์๋ก ๋ถ๋ฆฌ → ์ฟผ๋ฆฌ ์ต์ ํ ๊ฐ๋ฅ | ๋ก๋ ๋ฐธ๋ฐ์ฑ, ํ์ฅ์ฑ ↑ |
๋จ์ | JOIN/UPDATE ์ด๋ ค์ ํธ๋์ญ์ ๋ณต์ก |
์ค๋ฉ ํค ์ค๊ณ ์ด๋ ค์ |
- Range-based Sharding (๋ฒ์ ๊ธฐ๋ฐ ์ค๋ฉ)
๋ฐ์ดํฐ๋ฅผ ํน์ ๊ฐ(Shard Key)์ ํน์ ๋ฒ์์ ๋ฐ๋ผ ๋ถํ ํ๋ ๊ธฐ๋ฒ
ex. ์ข์ธก ์ค๋ article_id : 1 ~ 5,000, ์ฐ์ธก ์ค๋ article_id : 5,001 ~ 10,000
๋ฒ์ ๋ฐ์ดํฐ ์กฐํ ์ ๋ฆฌ
→ article_id 100๋ถํฐ 30๊ฐ ์กฐํํ๋ค๋ฉด ์ข์ธก ์ค๋์์ ๋ชจ๋ ๋ฐ์ดํฐ ์กฐํ ๊ฐ๋ฅ
๋ฐ์ดํฐ ์ ๋ฆผ ํ์ ๋ฐ์ ๊ฐ๋ฅ
→ ๋ฐ์ดํฐ๊ฐ 6,000๊ฐ ์๋ค๋ฉด, ์ฐ์ธก ์ค๋์๋ 1,000๊ฐ๋ง ์ ์ฅ
- Hash-based Sharding (ํด์ ๊ธฐ๋ฐ ์ค๋ฉ)
๋ฐ์ดํฐ๋ฅผ ํน์ ๊ฐ(Shard Key)์ ํด์ ํจ์์ ๋ฐ๋ผ ๋ถํ ํ๋ ๊ธฐ๋ฒ
ex. hash_function = article_id → article_id % 2 ๋ผ๋ฉด?
์ข์ธก ์ค๋ article_id = 1,3,5 ...
์ฐ์ธก ์ค๋ article_id = 2,4,6 ...
๋ฒ์ ๋ฐ์ดํฐ ์กฐํ ๋ถ๋ฆฌ
๋ฐ์ดํฐ ์ ๋ฆผ ํ์ ๋ฐฉ์ง ๊ฐ๋ฅ
- Directory-based Sharding (๋๋ ํ ๋ฆฌ ๊ธฐ๋ฐ ์ค๋ฉ)
๋ฐ์ดํฐ๋ฅผ ์ด๋ค ์ค๋์ ์ ์ฅํ ์ง๋ฅผ "์ค๋ฉ ํค ๊ธฐ๋ฐ ํด์"๋ก ์๋ ๊ณ์ฐํ๋ ๋์ ,
๋ช ์์ ์ธ ๋งคํ ํ ์ด๋ธ(๋๋ ํ ๋ฆฌ)์ ์ฌ์ฉํด์ ๊ด๋ฆฌํ๋ ๋ฐฉ์ - ์ด ์ธ ๋ค์ํ ์ค๋ฉ ๊ธฐ๋ฒ์ด ์กด์ฌ
- ๋ฌผ๋ฆฌ์ ์ค๋ & ๋ ผ๋ฆฌ์ ์ค๋
DB์ ํ์ฅ (2Shard → 4Shard)๋ก ์ธํด Client์ ์์ ๋ ํ์ํ๋ค. Client์ DB๋ ๋
๋ฆฝ์ ์ผ๋ก ๊ตฌ์ฑ ๋ฐ ์ด์๋ ์ ์๋ ์์คํ
์ด๋ค.
Client์ ์์ ์์ด DB๋ง ์ ์ฐํ๊ฒ ํ์ฅํ๋ ๋ฐฉ๋ฒ์ ์๋๊ฐ?
→ ๋
ผ๋ฆฌ์ ์ค๋์ ๋ฑ์ฅ
์ค์ ๋ฌผ๋ฆฌ์ ์ค๋์ ๊ฐ์์ ๊ด๊ณ์์ด, ๊ฐ๋
์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถํ ํ๋ ๊ฐ์์ ์ค๋ 4๊ฐ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ์.
๋ฌผ๋ฆฌ์ ์ค๋๋ 2๊ฐ์ด์ง๋ง, Client๋ 4๊ฐ์ ์ค๋๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ณ ๋ถํ ํ๋ ๊ฒ.
์ด๋ฐ ๊ฐ์์ ์ค๋๋ฅผ ๋
ผ๋ฆฌ์ ์ค๋๋ผ๊ณ ํ๋ค. ๋ฌผ๋ฆฌ์ ์ผ๋ก 2๊ฐ์ ์ค๋๋ก ๋ถ์ฐ๋์ง๋ง, Client๋ 4๊ฐ์ ์ค๋๋ก ๋ถ์ฐ๋ ๊ฒ์ผ๋ก ์ธ์งํ๋ค.
Client๋ ๋
ผ๋ฆฌ์ ์ค๋์ ์ ๊ทผ ๋ฐฉ๋ฒ์ผ๋ก๋ง DB์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด๋ค.
* hash_function = article_id → article_id % 4
๊ทธ๋ ๋ค๋ฉด Client๊ฐ ์์ฒญํ ๋
ผ๋ฆฌ์ ์ค๋๊ฐ ์ด๋ค ๋ฌผ๋ฆฌ์ ์ค๋์ ์ํด์๋์ง ์์์ผํ๋ค. ๊ทธ๋์ผ ์ง์ ์ ์ผ๋ก ๋ฐ์ดํฐ์ ์ ๊ทผ ๊ฐ๋ฅํ๋ค.
์ด๋ฌํ ๋ผ์ฐํ
์ ์ํ ๋ผ์ฐํฐ๋ DB์์คํ
๋ด๋ถ ๋๋ DB์ Client๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ ์ค๊ฐ์ ๋
๋ฆฝ์ ์ผ๋ก ๋ง๋ค์ด์ง ์ ์๋ค.
์ด๊ธฐ ์ํ:
๋ ผ๋ฆฌ ์ค๋ | ๋ฌผ๋ฆฌ ์ค๋ |
0, 1 | DB A |
2, 3 | DB B |
→ ๋
ผ๋ฆฌ์ ์ค๋ 4๊ฐ, ๋ฌผ๋ฆฌ์ DB 2๊ฐ์ ๋๋ ์ ์ฅ
→ ํด๋ผ์ด์ธํธ๋ ์ฌ์ ํ articleId % 4๋ง ํ๋ฉด ๋จ
DB๋ฅผ ํ์ฅํ๋ค๊ณ ๊ฐ์ :
๋ ผ๋ฆฌ ์ค๋ | ๋ฌผ๋ฆฌ ์ค๋ |
0 | DB A |
1 | DB B |
2 | DB C โฌ ๏ธ ์๋ก ์ถ๊ฐ |
3 | DB D โฌ ๏ธ ์๋ก ์ถ๊ฐ |
ํด๋ผ์ด์ธํธ๋ ์ฌ์ ํ articleId % 4๋ง ํ๋ฉด ๋จ → ์ฝ๋ ๋ณ๊ฒฝ ์์
* ๋ ผ๋ฆฌ ์ค๋์ ๋ฌผ๋ฆฌ ์ค๋ ๋งคํ๋ง ๋ผ์ฐํฐ๊ฐ ๋ฐ๊ฟ์ฃผ๋ฉด ๋จ
- ๋ ํ๋ฆฌ์นด
Primary & Replica / Leader & Follower / Master & Slave / Main & Standby
์ด๋ฐ ๋ณต์ ๋ ๋๊ธฐ์ ๋๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ๋ ์ ์๋ค.
Synchronous(๋๊ธฐ์ ) : ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๋, ์ฐ๊ธฐ ์ฑ๋ฅ์ด ์ ํ๋๋ค.
Asynchronous(๋น๋๊ธฐ์ ) : ์ฐ๊ธฐ ์ฑ๋ฅ์ด ์ ์ง๋๋, ๋ณต์ ๋ณธ์ ์ต์ ๋ฐ์ดํฐ๊ฐ ์ฆ์ ๋ฐ์๋์ง ์์ ์ ์๋ค.
- Article์ Shard Key๋ board_id๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํ๋ค.
์๋ํ๋ฉด ๋ง์ฝ article_id๋ฅผ Shard Key๋ก ํ๋ฉด (ex. article_id % 2) ๊ทธ๋ฌ๋ฉด ๊ฐ์ ๊ฒ์ํ์ ์๋ ๊ฒ์๊ธ์ธ๋ฐ๋ ์ฌ๋ฌ ์ค๋์์ ์กฐํํด ์์ผํ๋ค. Shard Key๊ฐ board_id ๋ฉด ๊ทธ๋ด ์ผ์ด ์๊ฒ ์ง. - Article ํ
์ด๋ธ์ PK๋ AUTO_INCREMENT ๋๋ UUID๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค.
๋ถ์ฐ ์์คํ ๊ณผ INDEX ๊ตฌ์กฐ์ ๋ํ ์ดํด๊ฐ ํ์ํ๋ค. (์ฐจํ ์ค๋ช )
์ค๋ฆ์ฐจ์ UNIQUE ์ซ์๋ฅผ ๋ง๋ค๊ธฐ ์ํ ์๊ณ ๋ฆฌ์ฆ? → ์ด๋ฏธ ๋ค์ํ๋ค (ex. Snowflake, TSID ๋ฑ) ์ด ์ค Snowflake๋ฅผ ์ฌ์ฉํด๋ณด์
- Snowflake ์๊ณ ๋ฆฌ์ฆ์ ๋ถ์ฐ์์คํ
์์๋ ๋น ๋ฅด๊ฒ ๋ง์ด ์ฌ์ฉํ๋ ์ค๋ฆ์ฐจ์ ์๊ณ ๋ฆฌ์ฆ
๋ถ์ํ๋ฉด <<,>> (๋นํธ ์ฌํํธ), |,& (๋นํธ ์ฐ์ฐ์), synchronized(๋๊ธฐํ) ๋ฑ์ ๋ํด ํ์ต
<< (๋นํธ ์ผ์ชฝ ์ํํธ)
1L << n : 1์ 2โฟ๋งํผ ์ํํธ
ex. 1 << 10 : 10000000000โโโ
| (๋นํธ ์ฐ์ฐ์)
11001โโโ | 10011โโโ : 11011โโโ
& (๋นํธ ์ฐ์ฐ์)
11001โโโ | 10011โโโ : 10001โโโ
// ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ๋ฉด ์ ๋๋ ์ฝ๋์ 'ํ๋์ ์ค๋ ๋'๋ง ๋ค์ด์ค๋๋ก
public synchronized long nextId() {
// ์ด ์์๋ ํ ๋ฒ์ ํ๋์ ์ค๋ ๋๋ง ๋ค์ด์ฌ ์ ์์
}
- open-in-view: false๋ lazy loading์ ๊ดํ ์ค์
: Lazy Loading์ ๊ด๊ณ๊ฐ ๊ฑธ๋ฆฐ ์ํฐํฐ๋ฅผ ๋ฐ๋ก ๋ถ๋ฌ์ค์ง ์๊ณ , "์ง์ง ํ์ํ ์๊ฐ๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ๊ทธ๋ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ ๊ฒ"
@Entity
class Post {
@OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments; // ๋๊ธ๋ค (์ฒ์์ ์ ๋ถ๋ฌ์ด)
}
์ ์ฝ๋์ฒ๋ผ Post๋ฅผ ๊ฐ์ ธ์ฌ ๋, comments๋ ์์ง ์ ๋ถ๋ฌ์์ง.
post.getComments()๋ฅผ ํธ์ถํ ๋ ๊ทธ์ ์ผ DB์์ ๊ฐ์ ธ์จ๋ค.
@GetMapping("/post/{id}")
public Post getPost(@PathVariable Long id) {
Post post = postRepository.findById(id).orElseThrow();
// ์ด ์์ ์ ํธ๋์ญ์
๋๋ฌ๊ณ , DB ์ธ์
๋ ๋ซํ
// ์๋ ์ฝ๋๊ฐ Lazy Loading์ ์ ๋ฐํ๋ฉด...
List<Comment> comments = post.getComments(); // โ ์ค๋ฅ ๋ฐ์
return post;
}
โ LazyLoading์ Hibernate Session์ด ์ด๋ ค ์์ด์ผ ๊ฐ๋ฅํจ
ํ์ง๋ง ๋ณดํต Controller์์๋ ์ด๋ฏธ ํธ๋์ญ์
์ด ๋๋ฌ๊ธฐ ๋๋ฌธ์
"Lazyํ๊ฒ ๋ถ๋ฌ์ค๋ ค๋ค๊ฐ ์ค๋ฅ ํฐ์ง" (์: LazyInitializationException)
๐ง ๊ทธ๋์ open-in-view: true๋ฉด?
Spring์ด ํธ๋์ญ์ ์ด ๋๋๋ DB ์ฐ๊ฒฐ์ ๋์ง ์๊ณ , ๋ทฐ ๋ ๋๋งํ ๋๊น์ง ์ ์งํด์ค
- ์ฆ, Controller, View ๊ณ์ธต์์๋ Lazy Loading์ด ๊ฐ๋ฅํด์ง
- ํธํ๊ธด ํ์ง๋ง, ์ปค๋ฅ์ ์ ์ค๋ ์ ์ ํด์ ์ฑ๋ฅ, ์์ ์ฑ ๋ฌธ์ ๊ฐ ์์ ์ ์์
๐ open-in-view: false๋?
ํธ๋์ญ์ ๋๋๋ฉด ๋ฐ๋ก DB ์ธ์ ์ ๋ซ์๋ฒ๋ฆผ
- Controller์์ Lazy Loading ์๋ → โ ์ค๋ฅ
- ๋์ , Service ๊ณ์ธต์์ ํ์ํ ๋ฐ์ดํฐ ๋ค ๋ฏธ๋ฆฌ ๋ก๋ฉํด์ผ ํจ
@PostService
public Post getPostWithComments(Long id) {
return postRepository.findByIdWithComments(id); // JOIN FETCH ์ฌ์ฉ
}
์ค์ | ์ค๋ช | Lazy Loading ์ฌ์ฉ ๊ฐ๋ฅ ์์น | ํน์ง |
true (๊ธฐ๋ณธ) | ๋ทฐ ๋ ๋๋ง ์๊น์ง DB ์ธ์ ์ ์ง | Controller, View๊น์ง ๊ฐ๋ฅ | ํธํ์ง๋ง ์ํ์ฑ ์์ |
false | ํธ๋์ญ์ ๋๋๋ฉด ์ธ์ ์ข ๋ฃ | Service ๊ณ์ธต๊น์ง๋ง ๊ฐ๋ฅ | ์์ ํ๊ณ ๋ช ํํ ์ค๊ณ ์ ๋ |
- ์ ๋น์ทํ Articleํด๋์ค๊ฐ 4๊ฐ๋ ์์๊น?
4๊ฐ์ ํด๋์ค๋ ๊ณ์ธต์ ์ญํ ๋ถ๋ฆฌ(Clean Architecture) ๊ด์ ์์ ์์ฃผ ์ค์ํ ์ญํ ์ ํด.
kuke.board.article
โโโ entity
โ โโโ Article.java ← JPA ์ํฐํฐ
โ
โโโ service
โ โโโ request
โ โ โโโ ArticleCreateRequest.java ← ์์ฑ ์์ฒญ DTO
โ โ โโโ ArticleUpdateRequest.java ← ์์ ์์ฒญ DTO
โ โ
โ โโโ response
โ โโโ ArticleResponse.java ← ์๋ต DTO
@Getter
public class ArticleCreateRequest {
private String title;
private String content;
private Long writerId;
private Long boardId;
}
๐ ๋ชฉ์
- ํด๋ผ์ด์ธํธ๊ฐ ์ ๊ฒ์๊ธ์ ์์ฑํ ๋ ๋ณด๋ด๋ JSON ๋ฐ์ดํฐ๋ฅผ ๋งคํ
- HTTP ์์ฒญ ๋ฐ๋(Request Body)์ ๋ด๊ธด ๋ด์ฉ์ ๋ฐ๋ ์ญํ
โ ์ฅ์
- ์ ํจ์ฑ ๊ฒ์ฆ ๊ฐ๋ฅ (@Valid, @NotNull, ๋ฑ)
- ์ธ๋ถ ์์ฒญ์๋ง ์ง์ค → ์ ๋ ฅ์ ์์ ์ฑ ํ๋ณด
@Getter
public class ArticleUpdateRequest {
private String title;
private String content;
}
๐ ๋ชฉ์
- ๊ฒ์๊ธ ์์ ์ ์ฌ์ฉํ ์์ฒญ ๊ฐ์ฒด
- ์์ ์์๋ title, content๋ง ํ์ํ๋ฏ๋ก, ํ์ํ ๋ฐ์ดํฐ๋ง ํฌํจ
โ ์ฅ์
- API ๋ช ์ธ๊ฐ ๋ช ํํจ (ํ๋๊ฐ ์ ์์๋ก ์ข์)
- "์์ ์์๋ ์ด๊ฒ๋ง ํ์ํ๋ค"๋ ์๋๋ฅผ ์ฝ๋๋ก ํํ
@Entity
public class Article {
@Id
private Long articleId;
private String title;
...
}
๐ ๋ชฉ์
- ์ง์ง DB์ ์ฐ๊ฒฐ๋ ๋ชจ๋ธ
- JPA๊ฐ ํ ์ด๋ธ๊ณผ ๋งคํํด์ ์์์ฑ ๊ด๋ฆฌ
- ๋น์ฆ๋์ค ๋ก์ง์ด ๋ค์ด๊ฐ๋ ๊ณณ (update(), create() ๋ฑ)
โ ์ฅ์
- DB์ ์ง์ ์ฐ๋ → ์ ์ฅ/์กฐํ/์์ ๊ฐ๋ฅ
- ๋๋ฉ์ธ ๋ก์ง ํฌํจ ๊ฐ๋ฅ (update(), delete(), isOwner() ๋ฑ)
@Getter
public class ArticleResponse {
private Long articleId;
private String title;
...
public static ArticleResponse from(Article article) { ... }
}
๐ ๋ชฉ์
- ์ปจํธ๋กค๋ฌ์์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ตํ ๋ ์ฌ์ฉํ๋ DTO
- Entity ๊ทธ๋๋ก ๋๊ธฐ์ง ์๊ธฐ ์ํด ์กด์ฌํจ (๋ณด์, ์์ ์ฑ)
โ ์ฅ์
- ํ์ํ ์ ๋ณด๋ง ๊ณจ๋ผ์ ์ค ์ ์์
- JSON ์ง๋ ฌํ์ ์์ ํจ (Lazy ๋ก๋ฉ ํ๋๋ ๋ฌดํ์ํ ๋ฌธ์ ์์)
- ํ์ํ๋ฉด ํ๋๋ช ๋ ๋ฐ๊ฟ์ ์ค ์ ์์ (title → articleTitle ๋ฑ)
๐ "์ ๋น์ทํ๋ฐ๋ ๊ตณ์ด ๋๋๊น?"
์ด์ | ์ค๋ช |
๊ณ์ธต ๊ฐ ์ญํ ๋ถ๋ฆฌ | ์์ฒญ DTO, ์๋ต DTO, ๋๋ฉ์ธ(Entity)์ ๋๋๋ฉด ์ฑ ์์ด ๋ช ํ |
๋ณด์ ๋ฐ ์์ ์ฑ | Entity๋ฅผ ๊ทธ๋๋ก ์๋ตํ๊ฑฐ๋, ์์ฒญ๋ฐ์ผ๋ฉด ์ํ (๋ ธ์ถ/์์ ๋ฌธ์ ๋ฑ) |
์ ์ฐํ ๋ณ๊ฒฝ | ์์ฒญ/์๋ต ํฌ๋งท ๋ฐ๋์ด๋ Entity๋ ๊ทธ๋๋ก ๋ ์ ์์ |
ํ ์คํธ ์ฌ์ | ๊ฐ ๊ณ์ธต์ ๋จ์ ํ ์คํธ๊ฐ ๊ฐ๋ฅํด์ง |
ํด๋ฆฐ ์ํคํ ์ฒ ์ฒ ํ | ๋ฐ์ดํฐ ํ๋ฆ์ "์ ๋ ฅ → ์ฒ๋ฆฌ → ์ถ๋ ฅ"์ผ๋ก ๋ช ํํ ๋๋ |
๐ก ๋น์ ๋ก ์ฝ๊ฒ ์ค๋ช ํ๋ฉด...
Article = "ํ์ฌ ๋ด๋ถ ์ ์ฐ DB ํ ์ด๋ธ"
- ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ๋ค ๋ค์ด์๊ณ , ๋ฏผ๊ฐํ ์ ๋ณด๋ ์์
- ์๋ฌด๋ ์ ๊ทผํ๊ฑฐ๋ ๋ฐ๊พธ๋ฉด ์ ๋จ
Create/Update Request = "๊ณ ๊ฐ์ด ์์ฑํ ์ ์ฒญ์"
- ๊ณ ๊ฐ์ด ์ฑ์์ผ ํ ํญ๋ชฉ๋ง ์์
Response = "๊ณ ๊ฐ์๊ฒ ๋ณด์ฌ์ฃผ๋ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ"
- ํ์ํ ์ ๋ณด๋ง ์์๊ฒ ๊ฐ๊ณตํด์ ๋ณด์ฌ์ค
- JSON ์ง๋ ฌํ์ ์์ ํจ (Lazy ๋ก๋ฉ ํ๋๋ ๋ฌดํ์ํ ๋ฌธ์ ์์) ์ด๊ฒ ๋ฌด์จ ๋ง์ด์ผ?
์ข์! ์ด๊ฑด JPA์์ ์ ๋ง ์์ฃผ ๋์ค๋ "Lazy ๋ก๋ฉ" + "๋ฌดํ ์ํ" ๋ฌธ์ ์ ๋ํ ์ค๋ช
์ด์ผ.
์์ฃผ ์์ฃผ ๋ง์ฃผ์น๋ ์ค์์ธ๋ฐ, JSON ์ง๋ ฌํํ ๋ ์ Entity ๋์ Response DTO๋ฅผ ์จ์ผ ํ๋์ง ์ดํดํ๋ ํต์ฌ์ด๊ธฐ๋ ํด.
โ ์ฉ์ด ๋จผ์ ์ง์
๐ ์ง๋ ฌํ(Serialization)?
- ์๋ฐ ๊ฐ์ฒด → JSON, XML, ๋ฌธ์์ด ๋ฑ์ผ๋ก ๋ณํํ๋ ๊ฒ
- ์: Article ๊ฐ์ฒด → JSON ์๋ต์ผ๋ก ๋ณํ
{
"articleId": 1,
"title": "Hello"
}
๐ข Lazy ๋ก๋ฉ์ด ๋ญ์๋๋ผ?
์๋ฅผ ๋ค์ด ๊ฒ์๊ธ → ๋๊ธ ๊ตฌ์กฐ:
@Entity
public class Article {
@OneToMany(mappedBy = "article", fetch = FetchType.LAZY)
private List<Comment> comments;
}
- fetch = LAZY → comments๋ ์ค์ ๋ก ์กฐํํ ๋๊น์ง DB์์ ์ ๋ถ๋ฌ์ด
article.getComments(); // ์ด ๋ DB์์ SELECT ์คํ
๐ฅ ๋ฌธ์ ์ํฉ
์ปจํธ๋กค๋ฌ์์ Entity๋ฅผ ๊ทธ๋๋ก ๋ฆฌํดํ๋ฉด:
@GetMapping("/article/{id}")
public Article getArticle(@PathVariable Long id) {
return articleRepository.findById(id).orElseThrow();
}
→ Spring MVC๊ฐ ์ด๊ฑธ JSON์ผ๋ก ์๋ ๋ณํ(์ง๋ ฌํ)ํ ๋, → ๋ด๋ถ์ ์ผ๋ก article.getComments()๋ฅผ ํธ์ถํจ
→ ๊ทธ๋ฐ๋ฐ ์ด ์์ ์ DB ์ธ์
์ ์ด๋ฏธ ๋ซํ ์์ → ๐ฅ LazyInitializationException
๐ ๋ฌดํ ์ํ ๋ฌธ์
๊ด๊ณ๊ฐ ์ด๋ ๊ฒ ๋์ด ์๋ค๋ฉด?
Article → List<Comment>
Comment → Article (์๋ฐฉํฅ ๋งคํ)
JSON ์ง๋ ฌํํ ๋:
Article → comments → each Comment → article → comments → ...
→ JSON ๋ณํ ๋์ค์ ๋ฌดํ ๋ฃจํ ๋ฐ์
→ ๐ฅ StackOverflowError, Jackson ์ํ ์ฐธ์กฐ ์ค๋ฅ ๋ฐ์
โ ํด๊ฒฐ ๋ฐฉ๋ฒ: DTO ๋ถ๋ฆฌ
ArticleResponse ๊ฐ์ DTO๋ ๊ทธ๋ฅ ํ๋ฉด ๊ตฌ์กฐ๋ก ํ๋๋ฅผ ๋ณต์ฌํ๊ธฐ ๋๋ฌธ์,
- Lazy ๋ก๋ฉ๋ ํ๋ ์ ๊ฑด๋๋ฆผ
- ์ํ ์ฐธ์กฐ ๊ตฌ์กฐ ์์
- JSON ๋ณํ ์ ์์
โ ์์ฝ ํ
์ํฉ | ๋ฌธ์ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
Entity ๊ทธ๋๋ก ๋ฆฌํด | Lazy ํ๋ ์กฐํ ์ ์์ธ ๋ฐ์ | DTO๋ก ๋ณํ |
Entity ์๋ฐฉํฅ ๊ด๊ณ | ์ํ ์ฐธ์กฐ ๋ฌดํ ๋ฃจํ | DTO์์ ํ์ํ ํ๋๋ง ์ฌ์ฉ |
์ฑ๋ฅ ์ต์ ํ | ํ์ ์๋ ํ๋๊น์ง ์กฐํ๋จ | ํ์ํ ์ ๋ณด๋ง DTO์ ๋ด์ |
๐ ๊ทธ๋์ ์ค๋ฌด์์๋ Entity → DTO ๋ณํํด์ ์๋ต ๋ณด๋ด๋ ๊ฒ "์์ ํ๊ณ ๊ถ์ฅ๋๋ ๋ฐฉ์"์ด์ผ.
์ํ๋ฉด @JsonIgnore, @JsonManagedReference, @JsonBackReference ๊ฐ์ ์ํ ์ฐธ์กฐ ํด๊ฒฐ ์ ๋ํ ์ด์ ๋ค๋ ์๋ ค์ค ์ ์์ด!
You said:
ChatGPT said:
์ง์ง ์ข์ ์ง๋ฌธ์ด์ผ!
"๋ด๊ฐ getComments()๋ฅผ ํธ์ถํ ์ ๋ ์๋๋ฐ, ์ด๋ป๊ฒ Spring์ด ๊ทธ๊ฑธ ํธ์ถํ๋๊ณ ?" ์ถ์ง?
๊ทผ๋ฐ ์ด๊ฑด Spring์ด ์๋์ผ๋ก ์ง๋ ฌํ๋ฅผ ํด์ฃผ๋ฉด์ ๋ฒ์ด์ง๋ ์ผ์ด์ผ. ํ๋์ฉ ํ์ด์ ์ค๋ช
ํด๋ณผ๊ฒ.
โ ์ํฉ ์ ๋ฆฌ
@GetMapping("/article/{id}")
public Article getArticle(@PathVariable Long id) {
return articleRepository.findById(id).orElseThrow();
}
์ฌ๊ธฐ์ Article ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐํํ์ด.
๊ทธ๋ฌ๋ฉด Spring์ ๋ญ ํ๋๋ฉด...
๐ Spring MVC๊ฐ ์๋์ผ๋ก ํด์ฃผ๋ ๊ณผ์
- Article ๊ฐ์ฒด → ํด๋ผ์ด์ธํธ์ JSON์ผ๋ก ๋ณํํด์ผ ํจ
- ์ด๋ Spring์ ๋ด๋ถ์ ์ผ๋ก Jackson(ObjectMapper) ์ ์ฌ์ฉํด์ ์ง๋ ฌํํด
ObjectMapper mapper = new ObjectMapper();
mapper.writeValueAsString(article);
- Jackson์ Article ํด๋์ค์ ๋ชจ๋ ํ๋์ ๋ํด getter()๋ฅผ ํธ์ถํด์ ๊ฐ์ ์ถ์ถํจ
article.getArticleId();
article.getTitle();
article.getComments(); ← ์ฌ๊ธฐ๊ฐ ๋ฌธ์ !
๐งจ ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ํฐ์ง
๐ฅ Lazy ๋ก๋ฉ ํ๋๋ ์ค์ DB ์กฐํ๋ฅผ "๊ทธ๋" ์๋ํจ
@OneToMany(fetch = FetchType.LAZY)
private List<Comment> comments;
- ์ด ํ๋๋ ์ฒ์์ ํ๋ก์ ๊ฐ์ฒด๋ก๋ง ๋ค์ด ์๊ณ ,
- ์ง์ง getComments()๊ฐ ํธ์ถ๋๋ ์๊ฐ DB ์กฐํ ์๋ํจ
- ๊ทธ๋ฐ๋ฐ ์ด๋ฏธ DB ์ธ์ ์ ๋ซํ ์์ด์ ์์ธ ๋ฐ์:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
๐ฆ ๊ทธ๋์ DTO๊ฐ ์ค์ํ ์ด์
public class ArticleResponse {
private Long articleId;
private String title;
// comments ์์
}
- DTO๋ ํ์ํ ํ๋๋ง ๋ฝ์์์ getComments() ๊ฐ์ ํธ์ถ์ด ์์ ์ ์ผ์ด๋จ
- ๊ทธ๋์ Lazy ํ๋, ์ํ ์ฐธ์กฐ ๋ฌธ์ ์์ ์์ ํ๊ฒ ๋ฒ์ด๋ ์ ์์
โ ์์ฝ
1 | Spring์ด Controller ๋ฆฌํด๊ฐ์ JSON์ผ๋ก ๋ฐ๊พธ๋ ค ํจ |
2 | ๋ด๋ถ์ ์ผ๋ก Jackson์ด getter()๋ฅผ ํธ์ถ |
3 | Lazy ํ๋์ getter ํธ์ถ → DB ์กฐํ ์๋ |
4 | ์ด ์์ ์ DB ์ธ์ ์ด ๋ซํ ์์ด์ ์์ธ ๋ฐ์ |
๐ ์ด๊ฑธ ๋ฐฉ์งํ๋ ค๊ณ Entity ๋์ DTO๋ฅผ ๋ง๋ค์ด์
์ง๋ ฌํ์ ํ์ํ ํ๋๋ง "๋ช
์์ ์ผ๋ก" ํฌํจํ๋ ๊ฒ ์ค๋ฌด์ ์ ์์ด์ผ.
- JPA์ ์์์ฑ์ปจํ ์คํธ๊ฐ update์ ํด์ฃผ๋ ์ผ
@Transactional
public ArticleResponse update(Long articleId, ArticleUpdateRequest request) {
Article article = articleRepository.findById(articleId).orElseThrow();
article.update(request.getTitle(), request.getContent());
return ArticleResponse.from(article);
}
Q: ์ด๊ฑฐ DB์ ๋ฐ์๋๋๊ฑฐ์ผ JPA๊ฐ ํด์ฃผ๋๊ฑฐ์ผ?
์ ํํ ์ง์์ด ๐
๊ฒฐ๋ก ๋ถํฐ ๋งํ๋ฉด:
โ DB์ ๋ฐ์๋๋ ๊ฑฐ ๋ง๊ณ , ๊ทธ ๋ฐ์์ JPA๊ฐ ์์์ ํด์ฃผ๋ ๊ฒ์ด์ผ.
๐ ์ฝ๋ ๋ค์ ๋ณด๊ธฐ
@Transactional
public ArticleResponse update(Long articleId, ArticleUpdateRequest request) {
Article article = articleRepository.findById(articleId).orElseThrow();
article.update(request.getTitle(), request.getContent()); // ← ์ฌ๊ธฐ์ ํ๋๋ง ๋ฐ๊ฟ
return ArticleResponse.from(article); // ์ ์ฅ ์ ํ๋๋ฐ??
}
โ๊ทผ๋ฐ save()๋ ์ ํ์์?
- articleRepository.save(article) ์ด๋ฐ ๊ฑฐ ์ ํด๋…
- ์ค์ ๋ก DB์ ๊ฐ์ด ๋ฐ์๋ผ!
๐ค ์ ๊ทธ๋ด๊น? → JPA์ ์์์ฑ ์ปจํ ์คํธ(Persistence Context) ๋๋ถ
1. findById()๋ก ์ํฐํฐ๋ฅผ ๊ฐ์ ธ์ค๋ฉด
→ JPA๋ ํด๋น ์ํฐํฐ๋ฅผ ์์ ์ํ๋ก ๊ด๋ฆฌํ๊ฒ ๋ผ
2. ๊ทธ ํ์ ์ํฐํฐ ํ๋๋ฅผ ๋ฐ๊พธ๋ฉด
→ JPA๋ "์ด ๊ฐ์ฒด๊ฐ ๋ฐ๋์๊ตฌ๋"๋ผ๊ณ ์ถ์ ํ๊ณ ์๋ค๊ฐ
3. @Transactional์ด ๋๋๋ ์์ (ํธ๋์ญ์ ์ข ๋ฃ)
→ flush()๊ฐ ์๋ ํธ์ถ๋๋ฉด์ DB์ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ํจ
(์ฆ, UPDATE ์ฟผ๋ฆฌ ์๋ ์คํ๋จ)
โ ํ๋ฆ ์์ฝ
1. findById()๋ก DB์์ ์ํฐํฐ ์กฐํ → ์์ ์ํ๋ก ๊ด๋ฆฌ ์์
2. article.update(...)๋ก ํ๋ ๋ณ๊ฒฝ
3. ํธ๋์ญ์
๋๋๋ ์์ ์ → JPA๊ฐ ๋ณ๊ฒฝ ๊ฐ์ง(dirty checking)
4. UPDATE SQL ์คํ๋จ
๐ฌ ๊ทธ๋์ save()๋ ์ธ์ ์ฐ๋?
์ํฉ | ์ฌ์ฉ ์์ | save() ํ์ ์ฌ๋ถ |
์ ์ํฐํฐ ์์ฑ | new Article() → save() | โ ํ์ |
์ด๋ฏธ ์์ ์ํ์ธ ๊ฐ์ฒด ์์ | findById() ํ ํ๋ ์์ | โ ํ์ ์์ (JPA๊ฐ ์๋ ๋ฐ์) |
๋น์์ ์ํ ์ํฐํฐ ์์ | detached ๊ฐ์ฒด | โ merge/save ํ์ |
๐ก ๊ฒฐ๋ก
- @Transactional ๋๋ถ์ ๋ณ๊ฒฝ ๊ฐ์ง + ์๋ DB ๋ฐ์์ด ์ผ์ด๋๋ ๊ฑฐ๊ณ ,
- ์ด๊ฒ JPA์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ ์ค ํ๋์ผ:
๐ "์์ ํ ๋ ๊ตณ์ด save() ์ ํด๋ ๋๋ค"
ํ์ํ๋ฉด Dirty Checking์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ด๋ถ ๊ตฌ์กฐ๋ ๊ทธ๋ฆผ์ผ๋ก ์ ๋ฆฌํด์ค๊ฒ!
You said:
ChatGPT said:
์ข์!
Dirty Checking(๋ํฐ ์ฒดํน)์ JPA์ ํต์ฌ ๊ธฐ๋ฅ ์ค ํ๋์ธ๋ฐ,
**"์์ ์ํ(Entity๊ฐ DB์ ์ฐ๊ฒฐ๋์ด ์๋ ์ํ)์์ ํ๋๊ฐ ๋ฐ๋๋ฉด JPA๊ฐ ์๋์ผ๋ก ๊ฐ์งํด์ UPDATE ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ ๊ฒ"**์ด์ผ.
๐ง Dirty Checking ํ๋ฆ ์ค๋ช (with ๊ทธ๋ฆผ)
โ ์์ ์ฝ๋
@Transactional
public void updateArticle(Long id, String newTitle) {
Article article = articleRepository.findById(id).orElseThrow();
article.setTitle(newTitle); // ํ๋ ๋ณ๊ฒฝ
// save() ํธ์ถ ์ ํจ
} // ํธ๋์ญ์
์ข
๋ฃ ์์ ์ DB ๋ฐ์๋จ
๐ฆ ๋ด๋ถ ๊ตฌ์กฐ: JPA๊ฐ ๋์ํ๋ ๋ฐฉ์
1. ํธ๋์ญ์
์์ (@Transactional)
2. findById → DB์์ ์กฐํ → 1์ฐจ ์บ์์ Article ์ ์ฅ
3. Article.setTitle("new title") ← ํ๋ ๊ฐ ๋ณ๊ฒฝ
4. ํธ๋์ญ์
์ข
๋ฃ → flush()
5. JPA ๋ด๋ถ ๋น๊ต → ๋ณ๊ฒฝ ๊ฐ์ง
6. UPDATE SQL ์คํ
๐จ ๊ทธ๋ฆผ์ผ๋ก ๋ณด๊ธฐ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ JPA ์์์ฑ ์ปจํ
์คํธ โ ← ํธ๋์ญ์
๋ด์์ ์ ์ง๋๋ ์บ์
โ โ
โ Article ์ํฐํฐ (ID=1) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ title = "old title" โ โ ← ์ค๋
์ท
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โฒ โฒ โ
โ โ โ โ
โ โ Article.setTitle("new title") ํธ์ถ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ title = "new title" โ โ ← ์ค์ ์ํฐํฐ ๊ฐ์ฒด
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ flush() ์์
โผ
๋ณ๊ฒฝ ๊ฐ์ง(Diff ๋น๊ต)
→ title ๋ณ๊ฒฝ๋จ ๊ฐ์ง
→ UPDATE article SET title = 'new title' WHERE id = 1 ์คํ
๐ ์ด๋ค ๋ฐฉ์์ผ๋ก ๊ฐ์งํ๋?
JPA๋ ์ค๋
์ท(snapshot) ์ ๋ง๋ค์ด ๋๊ณ ,
ํธ๋์ญ์
์ด ๋๋๊ธฐ ์ ์ flush() ํ ๋ → ์ค๋
์ท๊ณผ ํ์ฌ ์ํ๋ฅผ ๋น๊ต(diff) ํด.
if (!originalTitle.equals(currentTitle)) {
generateUpdateSql();
}
๐ก ์ ๋ฆฌ ์์ฝ
ํญ๋ชฉ | ์ค๋ช |
์ธ์ ? | @Transactional ๋ด์์ ์ํฐํฐ ํ๋ ๋ณ๊ฒฝ ์ |
์ด๋ป๊ฒ? | ์ด๊ธฐ ์ํ(์ค๋ ์ท)์ ํ์ฌ ์ํ ๋น๊ต |
๋ญ๊ฐ ๋ฐ์? | ์๋์ผ๋ก UPDATE SQL ์์ฑ๋จ |
save() ํ์? | โ ํ์ ์์ (์ด๋ฏธ ์์ ์ํ์ด๋ฏ๋ก) |
์ด๊ฒ JPA๊ฐ "๋๋ฉ์ธ ๋ชจ๋ธ ์ค์ฌ ๊ฐ๋ฐ"์ ์ฝ๊ฒ ํด์ฃผ๋ ์ด์ ์ค ํ๋์ผ.
์์ ์ ๊ทธ๋ฅ ๊ฐ์ฒด์ฒ๋ผ ๋ค๋ฃจ๊ธฐ๋ง ํ๋ฉด ๋๊ณ , DB์ ๋ฐ์์ JPA๊ฐ ์์์ ํด์ฃผ๋๊น.
ํ์ํ๋ฉด Dirty Checking ๋ฌดํจํ๋๋ ๊ฒฝ์ฐ๋ ์ ๋ฆฌํด์ค๊ฒ (์: detached ์ํ, flush ์์ ๋ฑ).
๐ API ํ ์คํธ์์ ์ฌ์ฉํ๋ RestClient
ChatGPT said:
์ข์, ์์ฃผ ์ข์ ์ง๋ฌธ์ด์ผ!
RestClient๋ ์ต๊ทผ Spring์์ ๋์
๋ HTTP ํด๋ผ์ด์ธํธ ๋๊ตฌ ์ค ํ๋์ธ๋ฐ,
๋ "ํ๋ฉด ์์ด API ํ
์คํธํ ๋ ์ฐ์ธ๋ค"๊ณ ๋ดค๋ค๊ณ ํ์ง?
→ ๊ทธ๊ฑด ์ ํํด. ์ง๊ธ๋ถํฐ ์ ๋ฆฌํด์ค๊ฒ.
โ RestClient๋?
Spring 6.1๋ถํฐ ๋์ ๋ ์๋ก์ด HTTP ํด๋ผ์ด์ธํธ
๊ธฐ์กด์ RestTemplate, WebClient๋ณด๋ค ๊ฐ๊ฒฐํ๊ณ ํ ์คํธ ์นํ์ ์ธ API ์ ๊ณต
๐ฆ ์ธ์ ์ฐ์ด๋๋ฉด?
- ํ๋ก ํธ ํ๋ฉด ์์ด ๋ฐฑ์๋ API๋ง ํ ์คํธํ๊ฑฐ๋ ํธ์ถํ๊ณ ์ถ์ ๋
- ์๋ฅผ ๋ค์ด, "๊ฒ์๊ธ ์์ฑ API", "๋ก๊ทธ์ธ API"๊ฐ ์ ์๋ํ๋์ง ํ์ธํ ๋
- ๋๋ ๋ค๋ฅธ ์ธ๋ถ API๋ฅผ ํธ์ถํด์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ฌ ๋๋ ์ฌ์ฉ
โ ๊ฐ๋จํ ์์ (Spring Boot)
RestClient client = RestClient.create();
ArticleCreateRequest request = new ArticleCreateRequest("์ ๋ชฉ", "๋ด์ฉ", 1L, 2L);
ArticleResponse response = client.post()
.uri("http://localhost:8080/api/articles")
.body(request)
.retrieve()
.body(ArticleResponse.class);
๐ง ์๋ฏธ
- RestClient.create() → HTTP ์์ฒญ์ ๋ณด๋ผ ์ค๋น
- .post().uri(...) → POST ์์ฒญ์ ์ด URL๋ก ๋ณด๋
- .body(request) → JSON์ผ๋ก ๋ณํ๋์ด ์ ์ก๋จ
- .retrieve().body(...) → ์๋ต์ ํน์ ๊ฐ์ฒด๋ก ํ์ฑํด์ ๋ฐ์
๐ก ์ RestClient๊ฐ ์ฃผ๋ชฉ๋ฐ๋๋ฉด
๋น๊ต ๋์ | ํน์ง |
RestTemplate | ์๋ ๋ฐฉ์. ๊ณง deprecated ๋ ์์ |
WebClient | ๋น๋๊ธฐ ์ง์๋จ. ๊ทธ๋ฌ๋ ์ฝ๊ฐ ๋ณต์กํจ |
RestClient | ๊ฐ์ฅ ๊ฐ๊ฒฐํ๊ณ ์ง๊ด์ ์. ํ ์คํธ๋ ๊ฐ๋จ ํธ์ถ์ ์ข์ |
๐งช ๊ทธ๋ผ "API ํ ์คํธ์ฉ"์ผ๋ก ์ด๋ป๊ฒ ์ฐ๋?
์: API ํตํฉ ํ ์คํธ์์ ์ง์ API ํธ์ถํ๊ณ ๊ฒฐ๊ณผ ๊ฒ์ฆ
@Test
void ๊ฒ์๊ธ_์์ฑ_API_ํ
์คํธ() {
ArticleCreateRequest request = new ArticleCreateRequest("์ ๋ชฉ", "๋ด์ฉ", 1L, 2L);
ArticleResponse response = RestClient.create()
.post()
.uri("http://localhost:8080/api/articles")
.body(request)
.retrieve()
.body(ArticleResponse.class);
assertEquals("์ ๋ชฉ", response.getTitle());
}
- ํ๋ฉด ์์ด API๊ฐ ์ ์๋ํ๋์ง ๊ฒ์ฆ ๊ฐ๋ฅ
- Postman์ด๋ Swagger ์์ด ์ฝ๋๋ก ํ ์คํธ
โ ์์ฝ
ํญ๋ชฉ | ์ค๋ช |
์ด๋ฆ | RestClient |
์ฉ๋ | API ํธ์ถ, ํ ์คํธ, ์ธ๋ถ ์๋ฒ ํต์ |
์ฅ์ | ๊ฐ๊ฒฐํ๊ณ ์ง๊ด์ ์ธ ๋ฌธ๋ฒ, ํ ์คํธ ์นํ์ |
๋์ | Spring 6.1๋ถํฐ ์ง์ |
์ํ๋ฉด RestTemplate, WebClient, RestClient 3๊ฐ์ง ๋น๊ตํ๋ ๋ง๋ค์ด์ค๊ฒ!
๋๋ ์ค์ ํ
์คํธ ํด๋์ค ๊ตฌ์กฐ๋ ๋ณด์ฌ์ค ์ ์์ด.
๐ @SpringBootTest ์ ์๋ฏธ
@SpringBootTest๋ Spring Boot์์ ํ
์คํธํ ๋ ์ฌ์ฉํ๋ ํตํฉ ํ
์คํธ์ฉ ์ ๋ํ
์ด์
์ด์ผ.
๋จ์ ํ
์คํธ๋ณด๋ค ํ ๋จ๊ณ ๋ ์ฌ๋ผ๊ฐ ์์ค์ผ๋ก, ์คํ๋ง ์ปจํ
์คํธ ์ ์ฒด๋ฅผ ๋ก๋ฉํด์ ์ค์ ์ฒ๋ผ ํ
์คํธํ ์ ์์ด.
โ ํ ์ค ์์ฝ
Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด๋ฅผ ์คํํ ์ํ๋ก ํ ์คํธํ๋ ์ ๋ํ ์ด์
๐ง ์ธ์ ์ฐ์ด๋?
- @Service, @Repository, @Controller ๊ฐ์ ๋น๋ค์ด ์ค์ ์ฒ๋ผ ๋ค ๋ฑ๋ก๋ ์ํ์์ ํ ์คํธํ๊ณ ์ถ์ ๋
- ์: DB ์ฐ๋, ํธ๋์ญ์ , ์์กด์ฑ ์ฃผ์ , API ํ ์คํธ ๋ฑ
๐งช ์์ ์ฝ๋
@SpringBootTest
public class ArticleServiceTest {
@Autowired
ArticleService articleService;
@Test
void createArticle() {
articleService.create(...);
}
}
- SpringBootTest ๋๋ถ์ ArticleService๊ฐ @Autowired ๋จ
- ํธ๋์ญ์ , JPA, ์คํ๋ง ์ค์ ๋ค ์ ์ฉ๋จ
โ๏ธ ๋ด๋ถ์ ์ผ๋ก ํ๋ ์ผ
@SpringBootTest๋ ๋ค์์ ํฌํจํด:
- @ExtendWith(SpringExtension.class)
- ์คํ๋ง ApplicationContext ์ ์ฒด ๋ก๋
- @Autowired, @Transactional ์ฌ์ฉ ๊ฐ๋ฅ
- ํ์ํ๋ฉด application.yml๋ ์ ์ฉ๋จ
โ ์ถ๊ฐ ์ต์
์ต์ | ์ค๋ช |
webEnvironment = NONE | ์น ํ๊ฒฝ ์์ (๊ธฐ๋ณธ๊ฐ) |
webEnvironment = MOCK | MockServlet ํ๊ฒฝ ์ฌ์ฉ |
webEnvironment = RANDOM_PORT | ์ค์ HTTP ์๋ฒ ์คํ (๋๋ค ํฌํธ) |
webEnvironment = DEFINED_PORT | ์ค์ HTTP ์๋ฒ ์คํ (application.yml์ ์ ์๋ ํฌํธ ์ฌ์ฉ) |
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
→ ์ด๋ฌ๋ฉด ์ง์ง ์๋ฒ ๋์์ RestTemplate, RestClient, TestRestTemplate๋ก API ํ ์คํธ ๊ฐ๋ฅ
โ ์ธ์ ์ ์ฐ๋ ๊ฒ ์ข์๊น?
- ๋จ์ ํ ์คํธ์์๋ ๋๋ฌด ๋ฌด๊ฑฐ์ (์ปจํ ์คํธ ์ ์ฒด ๋ก๋ฉํ๋๊น ๋๋ฆผ)
- ๋จ์ ๋ก์ง ํ ์คํธ๋ @WebMvcTest, @DataJpaTest, @MockBean ๋ฑ์ ์จ์ ๊ฐ๋ณ๊ฒ ํ ์คํธํ๋ ๊ฒ ์ข์
๐ ์ ๋ฆฌ
ํญ๋ชฉ | ์ค๋ช |
์ญํ | ์คํ๋ง ์ปจํ ์คํธ ์ ์ฒด ๋ก๋ฉํด์ ํ ์คํธ |
์ฃผ๋ก ํ ์คํธ ๋์ | ์๋น์ค, ๋ฆฌํฌ์งํ ๋ฆฌ, API, ํตํฉ ์๋๋ฆฌ์ค |
์ฅ์ | ์ค์ ์คํ ํ๊ฒฝ๊ณผ ๊ฑฐ์ ๋์ผํ๊ฒ ํ ์คํธ ๊ฐ๋ฅ |
๋จ์ | ๋๋ฆผ. ๋จ์ ํ ์คํธ๋ณด๋ค ๋ฆฌ์์ค ๋ง์ด ์ |
๐ @PersistenceContext, EntityManager, TransactionTemplate, CountDownLatch, ExecuterService, Executers.newFixedThreadPool() ์ค๋ช
package kuke.board.article.data;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import kuke.board.article.entity.Article;
import kuke.board.common.snowflake.Snowflake;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SpringBootTest
public class DataInitializer {
@PersistenceContext
private EntityManager entityManager;
@Autowired
TransactionTemplate transactionTemplate;
Snowflake snowflake = new Snowflake();
CountDownLatch latch = new CountDownLatch(EXCUTE_COUNT);
static final int BULK_INIT_SIZE = 2000;
static final int EXCUTE_COUNT = 6000;
@Test
void initialize() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < EXCUTE_COUNT; i++) {
executorService.submit(() -> {
insert();
latch.countDown();
System.out.println("latch.getCount() = " + latch.getCount());
});
}
latch.await();
executorService.shutdown();
}
void insert() {
transactionTemplate.executeWithoutResult(status -> {
for (int i = 0; i < BULK_INIT_SIZE; i++) {
Article article = Article.create(
snowflake.nextId(),
"title" + i,
"content" + i,
1L,
1L
);
entityManager.persist(article);
}
});
}
}
์์ ์ข์ ํฌ์ธํธ๋ค์ด์ผ ๐
์ด๊ฑด ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ณ๋ ฌ๋ก ์ด๊ธฐํํ๋ ์ฝ๋์ธ๋ฐ,
๋งํ ํค์๋๋ค์ด ์ ๋ถ ํต์ฌ ์์๋ค์ด๋ผ ํ๋์ฉ ๊น๋ํ๊ฒ ์ค๋ช
ํด์ค๊ฒ!
โ @PersistenceContext + EntityManager
๐ธ @PersistenceContext
- JPA๊ฐ ์ ๊ณตํ๋ EntityManager๋ฅผ ์ฃผ์ ํด์ฃผ๋ ์ ๋ํ ์ด์
- @Autowired๋ ๋น์ทํ์ง๋ง, JPA ํ์ค ๋ฐฉ์ (์คํ๋ง ๋ถํธ์์ ๊ฑฐ์ ๊ฐ๊ฒ ๋์ํจ)
@PersistenceContext
private EntityManager entityManager;
๐ธ EntityManager
- JPA์ ํต์ฌ ํด๋์ค
- persist, find, remove, flush ๋ฑ DB ์์ ์ ์ง์ ์ํํจ
- entityManager.persist(entity) → INSERT SQL ์คํ๋จ
โ TransactionTemplate
๐ ์ค๋ช
- ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ํธ๋์ญ์ ์ ์ ์ดํ ์ ์๊ฒ ํด์ฃผ๋ Spring ํด๋์ค (๊ฐ๋ฐ์๊ฐ ์ฝ๋๋ก ํธ๋์ญ์ ์ ์์๊ณผ ๋์ ์ง์ ์ ์ด)
- ์ด๋ ธํ ์ด์ (@Transactional) ๋์ ์ฝ๋๋ก ์ง์ ํธ๋์ญ์ ์ ๊ด๋ฆฌํ ์ ์์
๐ ์์
transactionTemplate.executeWithoutResult(status -> {
// ์ฌ๊ธฐ๋ ํธ๋์ญ์
์
entityManager.persist(...);
});
- ๋ด๋ถ ์ฝ๋๊ฐ ํธ๋์ญ์ ์์์ ์คํ๋๊ณ , ์์ธ๊ฐ ๋๋ฉด ์๋ ๋กค๋ฐฑ๋จ
๐ ๋น๊ตํด๋ณผ๊ฒ:
โ ์ ์ธ์ ๋ฐฉ์ (@Transactional)
- ์ฝ๋ ์์ @Transactional๋ง ๋ถ์ด๋ฉด, Spring์ด ์์์ ํธ๋์ญ์ ์ ์์ํ๊ณ ์ปค๋ฐ/๋กค๋ฐฑํด์ค.
@Transactional
public void doSomething() {
// ํธ๋์ญ์
์์์ ์คํ๋จ
entityManager.persist(...);
}
- ๊ฐํธํ์ง๋ง, ๋ณต์กํ ํธ๋์ญ์ ํ๋ฆ์ ์ง์ ์กฐ์ ํ๊ธฐ๋ ์ด๋ ค์
โ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ (TransactionTemplate)
- ์ง์ ์ฝ๋๋ก ํธ๋์ญ์ ์ ๋ฒ์๋ฅผ ์ ์ํ๊ณ , ๊ทธ ์์์ ์ํ๋ ์ฒ๋ฆฌ๋ฅผ ํจ
transactionTemplate.executeWithoutResult(status -> {
// ํธ๋์ญ์
์์๋จ
entityManager.persist(...);
// ์์ธ ๋ฐ์ ์ ์๋ ๋กค๋ฐฑ
});
- ์ด ๋ฐฉ์์ ์ฅ์ :
- ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ํธ๋์ญ์ ๋ฒ์๋ฅผ ๋ฐ๊ฟ ์ ์์
- try-catch๋ก ์ ๊ตํ ์์ธ ์ฒ๋ฆฌ ๊ฐ๋ฅ
- ๋ด๋ถ์์ rollback ์ ํ๋ ๊ฐ๋ฅ
status.setRollbackOnly();
โ CountDownLatch
๐ ์ค๋ช
- ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์์ ์ ์คํํ๊ณ ,
- ๋ชจ๋ ์์ ์ด ๋๋ ๋๊น์ง ๋ฉ์ธ ์ค๋ ๋๊ฐ ๊ธฐ๋ค๋ฆฌ๊ฒ ํ๋ ๋๊ตฌ
๐ ์๋ ๋ฐฉ์
CountDownLatch latch = new CountDownLatch(6000);
- ์ค๋ ๋๊ฐ ์์ ํ ๋๋ง๋ค latch.countDown();
- ๋ฉ์ธ ์ค๋ ๋๋ latch.await(); ๋ก ๊ธฐ๋ค๋ฆผ
์ฆ, 6000๊ฐ์ insert ์์ ์ด ๋๋ ๋๊น์ง initialize()๊ฐ ๋๋์ง ์์
โ ExecutorService + Executors.newFixedThreadPool()
๐ธ ExecutorService
- ์๋ฐ์ ์ค๋ ๋ ํ ๊ด๋ฆฌ ๋๊ตฌ
- ์ค๋ ๋๋ฅผ ์ง์ ์์ฑํ์ง ์๊ณ , ํ์์ ๊บผ๋ด์ ์คํ ๊ฐ๋ฅ
ExecutorService executorService = Executors.newFixedThreadPool(10);
- ์ต๋ 10๊ฐ์ ์ค๋ ๋๊ฐ ๋์์ ์คํ ๊ฐ๋ฅ
- executorService.submit(() -> {...}) → ๋น๋๊ธฐ๋ก ์์ ์คํ
โ ์ด ์ฝ๋์ ์ ์ฒด ๊ตฌ์กฐ๋ฅผ ์ ๋ฆฌํ๋ฉด?
๐ ๋ชฉํ:
→ 2000๊ฐ์ฉ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ insert()๋ฅผ 6000๋ฒ ๋ณ๋ ฌ๋ก ๋๋ ค์ ๋๋ ๋ฐ์ดํฐ ์ด๊ธฐํ
๐งฑ ๊ตฌ์ฑ:
๊ตฌ์ฑ ์์ | ์ญํ |
EntityManager | JPA๋ก INSERT ์คํ |
TransactionTemplate | ํธ๋์ญ์ ์ง์ ์ ์ด (@Transactional ์์ด๋ ์์ ํ๊ฒ) |
ExecutorService | 10๊ฐ์ ์ค๋ ๋๋ก ๋ณ๋ ฌ ์ฒ๋ฆฌ |
CountDownLatch | ๋ชจ๋ ์์ ๋๋ ๋๊น์ง ๋ฉ์ธ ์ค๋ ๋ ๋๊ธฐ |
Snowflake | ID ์ค๋ณต ์์ด ์์ฑํ๊ธฐ ์ํ ๊ณ ์ ID ์์ฑ๊ธฐ |
โ๏ธ ๊ฐ๋จ ์์ฝ
ํค์๋ | ์ค๋ช |
@PersistenceContext | JPA์ EntityManager ์ฃผ์ |
EntityManager | JPA์ DB ์์ ๋๊ตฌ (persist, find, etc) |
TransactionTemplate | ๋ช ์์ ์ผ๋ก ํธ๋์ญ์ ๋ฒ์ ์ ์ด |
CountDownLatch | ๋ชจ๋ ์ค๋ ๋ ์์ ์๋ฃ ๋๊ธฐ์ฉ |
ExecutorService | ์ค๋ ๋ ํ๋ก ๋ฉํฐ์ค๋ ๋ฉ ์ฒ๋ฆฌ |
Executors.newFixedThreadPool(10) | ์ต๋ 10๊ฐ์ ์ค๋ ๋๋ก ๋์์ ์คํ |
ํ์ํ๋ฉด ์ด ๊ตฌ์กฐ๋ฅผ @Transactional, @Async, TestContainers, Benchmark ์ชฝ์ผ๋ก ํ์ฅํ๋ ๋ฐฉ๋ฒ๋ ์๋ ค์ค๊ฒ!
๐ transactionTemplate ์ด ์์ผ๋ฉด entityManager.persist(); ๊ฐ ํจ๊ณผ ์๋?
void insert() {
transactionTemplate.executeWithoutResult(status -> {
for (int i = 0; i < BULK_INSERT_SIZE; i++) {
Article article = Article.create(
snowflake.nextId(),
"title" + i,
"content" + i,
1L,
1L
);
entityManager.persist(article);
}
});
}
์ข์, ์ด ์ง๋ฌธ ํต์ฌ์ด์ผ.
๋ฐ๋ก ๋๋ตํ๋ฉด:
โ transactionTemplate์ด ์๋ค๊ณ entityManager.persist()๋ฅผ ๋ชป ์ฐ๋ ๊ฑด ์๋์ผ.
ํ์ง๋ง → ํธ๋์ญ์ ์ด ์์ผ๋ฉด persist()๋ง์ผ๋ก๋ ์ค์ ๋ก DB์ ์์ ํ๊ฒ insert๋์ง ์์ ์๋ ์์ด.
์ด๊ฑธ ์ ๋๋ก ์ดํดํ๋ ค๋ฉด, JPA์์ ํธ๋์ญ์ ์ด ์ด๋ค ์ญํ ์ ํ๋์ง ๋จผ์ ๋ด์ผ ํด.
โ JPA์์ persist()์ ๋ณธ์ง
entityManager.persist(entity);
์ด๊ฑด ๋จ์ํ **"์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ
์คํธ์ ๋ฑ๋ก"**ํ๋ค๋ ๋ป์ด์ผ.
์ฆ, ๋ฉ๋ชจ๋ฆฌ ์์ค์ ์์
์ด์ผ.
ํ์ง๋ง ์ง์ง๋ก DB์ INSERT SQL์ด ๋ ์๊ฐ๊ณ ์ปค๋ฐ๋๋ ๊ฑด:
โ ํธ๋์ญ์ ์ด ์ด๋ ค ์์ ๋ + ์ปค๋ฐ๋ ๋
โ ํธ๋์ญ์ ์์ด persist()๋ง ํธ์ถํ๋ฉด?
- EntityManager.persist()๋ ํธ์ถ๋์ง๋ง,
- flush()๋ commit()์ด ํธ์ถ๋์ง ์์ผ๋ฉด ์ค์ DB์๋ ๋ฐ์๋์ง ์์
- ์ฆ, ํ ์คํธ ์ฝ๋์์ ์๋ฌด ์ผ๋ ์ ์ผ์ด๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ์ ์์
๐ฏ ๊ทธ๋์ transactionTemplate์ด ํ์ํ ์ด์ ๋?
๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์๋ @Transactional์ ์ฐ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์
→ TransactionTemplate์ ์จ์ ๋ช
์์ ์ผ๋ก ํธ๋์ญ์
๊ฒฝ๊ณ๋ฅผ ์ ํด์ฃผ๋ ๊ฑฐ์ผ.
transactionTemplate.executeWithoutResult(status -> {
entityManager.persist(...); // ์ด์ ํธ๋์ญ์
์์์ ์์ ํ๊ฒ ์คํ๋จ
});
๐ ๋น๊ต ์์ฝ
์ผ์ด์ค | persist() ์คํ ๊ฐ๋ฅ? | ์ค์ DB ๋ฐ์๋จ? | ๋น๊ณ |
ํธ๋์ญ์ ์์ | ๊ฐ๋ฅ (์ค๋ฅ ์๋) | โ ์ ๋จ | flush/commit ์์ |
ํธ๋์ญ์ ์์ (@Transactional ๋๋ TransactionTemplate) | โ ๊ฐ๋ฅ | โ DB ๋ฐ์๋จ | ํธ๋์ญ์ ๋์ flush๋จ |
โ ์ ๋ฆฌ
- entityManager.persist()๋ ํธ๋์ญ์ ์ด ์์ด์ผ ์๋ฏธ ์์
- TransactionTemplate ์์ด๋ ํธ์ถ์ ๊ฐ๋ฅํ์ง๋ง, DB ๋ฐ์์ ์ ๋จ
- ๋ฉํฐ์ค๋ ๋์์๋ @Transactional์ด ์ ์ฉ ์ ๋๊ธฐ ๋๋ฌธ์, TransactionTemplate์ด ๋ฐ๋์ ํ์
๊ถ๊ธํ๋ฉด flush(), commit(), rollback์ ์ฐจ์ด๋ ๋ ์ค๋ช
ํด์ค๊ฒ!
๋๋ persist() vs merge() ์ฐจ์ด๋ ์์ฃผ ๋ฌป๋ ํฌ์ธํธ์ผ.
๐ docker exec -it kuke_board_mysql bash ๋ช ๋ น์ด ์๋ฏธ
์ข์! ์ด ๋ช ๋ น์ด๋ Docker์์ ์คํ ์ค์ธ ์ปจํ ์ด๋ ์์ ๋ค์ด๊ฐ๋ ๋ช ๋ น์ด์ผ.
๐งพ docker exec -it kuke_board_mysql bash
โ ๊ตฌ์ฑ ์์๋ณ ํด์
ํํธ | ์๋ฏธ |
docker | Docker CLI ๋ช ๋ น์ด |
exec | ์คํ ์ค์ธ ์ปจํ ์ด๋ ์์์ ๋ช ๋ น์ด๋ฅผ ์คํํ๊ฒ ๋ค๋ ๋ป |
-i | interactive ๋ชจ๋ — ์ ๋ ฅ์ ๋ฐ์ ์ ์๊ฒ ํด์ค |
-t | pseudo-TTY — ํฐ๋ฏธ๋์ฒ๋ผ ๋ณด์ด๊ฒ ํด์ค (ํ๋ฉด ์ถ๋ ฅ ์์๊ฒ) |
kuke_board_mysql | ์ปจํ ์ด๋ ์ด๋ฆ (๋๋ ID) |
bash | ์ปจํ ์ด๋ ์์์ ์คํํ ๋ช ๋ น์ด (์ฌ๊ธฐ์ bash ์) |
'infra & network' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Lecture] ์คํ๋ง๋ถํธ๋ก ์ง์ ๋ง๋ค๋ฉด์ ๋ฐฐ์ฐ๋ ๋๊ท๋ชจ ์์คํ ์ค๊ณ - (2) (0) | 2025.04.20 |
---|---|
HTTPS๋ (SSL / TLS) (0) | 2024.08.11 |
SOP & CORS๋? (0) | 2024.07.31 |
[Network] ๋ง์ฝ DNS๊ฐ ์์ฒญํ ๋๋ฉ์ธ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ง ์๋ค๋ฉด? (0) | 2024.06.09 |
[Network] ์น ๋ธ๋ผ์ฐ์ ์ URL์ ์ ๋ ฅํ๋ค๋ฉด ์ผ์ด๋๋ ์ผ (2) | 2024.05.19 |
๋๊ธ