Skip to content

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ๋ชจ์นด(์ตœ์˜์ฒ ) ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#579

Open
softmoca wants to merge 44 commits into
woowacourse:softmocafrom
softmoca:step3-jpa
Open

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ๋ชจ์นด(์ตœ์˜์ฒ ) ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#579
softmoca wants to merge 44 commits into
woowacourse:softmocafrom
softmoca:step3-jpa

Conversation

@softmoca

Copy link
Copy Markdown

์ฒดํฌ ๋ฆฌ์ŠคํŠธ

  • ๋ฏธ์…˜์˜ ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ–ˆ๋‚˜์š”?
  • Gradle test๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ํ†ต๊ณผํ–ˆ๋‚˜์š”?
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜๋‚˜์š”?

๋ฒ ์ด์Šค ์ฝ”๋“œ ์„ ํƒ ์ฒดํฌ

  • ์ด์ „ ๋ฏธ์…˜์˜ ๋‚ด ์ฝ”๋“œ์—์„œ ์‹œ์ž‘
  • ์ด์ „ ๋ฏธ์…˜์˜ ํŽ˜์–ด์˜ ์ฝ”๋“œ์—์„œ ์‹œ์ž‘

์–ด๋–ค ๋ถ€๋ถ„์— ์ง‘์ค‘ํ•˜์—ฌ ๋ฆฌ๋ทฐํ•ด์•ผ ํ• ๊นŒ์š”?

0๋‹จ๊ณ„ โ€” ๋ฒ”์œ„

  • ์‹œ์ž‘ ๋ธŒ๋žœ์น˜: step2 / ์ž‘์—… ๋ธŒ๋žœ์น˜: step3-jpa
  • ๊ฑด๋“œ๋ฆฐ ๋ฒ”์œ„: adapter/persistence(์–ด๋Œ‘ํ„ฐยท*JpaRepository) + domain(์—”ํ‹ฐํ‹ฐ 4์ข… ๋งคํ•‘ ์–ด๋…ธํ…Œ์ด์…˜) + ์„ค์ •(ddl-auto=create-dropยทSQL ๋กœ๊น…)
  • ์œ ์ง€(๋ถˆ๋ณ€): adapter/webยทapplication(API ๋ช…์„ธยทDTO)ยท๋„๋ฉ”์ธ ์ •์ฑ…ยท์˜ˆ์™ธ ๊ณ„์ธตยทreservation/waiting ๋ถ„๋ฆฌ ๋ชจ๋ธ โ€” 0๋‹จ๊ณ„ "์žฌ๊ตฌํ˜„ ๊ธˆ์ง€" ์ค€์ˆ˜

๋‹จ๊ณ„๋ณ„ ๋„๋‹ฌ ์ง€์ 

  • 1๋‹จ๊ณ„ โœ… ๋งคํ•‘(ThemeยทReservationTime) โ†’ ์—ฐ๊ด€๊ด€๊ณ„(ReservationยทWaiting ๋‹จ๋ฐฉํ–ฅ @ManyToOne) โ†’ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ 6๊ด€์ฐฐ(dirty checkingยท1์ฐจ์บ์‹œยท์“ฐ๊ธฐ์ง€์—ฐยทflushยทfetch๊ธฐ๋ณธยทLazyInit)
  • 2๋‹จ๊ณ„ โœ… ๋‚ด ์˜ˆ์•ฝ ๋ชฉ๋ก โ€” name ์œ ์ง€ + ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ํŒŒ์ƒ ์ฟผ๋ฆฌ(๊ธฐ๋Šฅ์€ 1-2์—์„œ ์ถฉ์กฑ, mine N+1 ๊ด€์ฐฐ)
  • 3๋‹จ๊ณ„ โœ… 3-1 N+1 โ†’ @EntityGraph fetch join(mine 8โ†’2) / 3-2 ์ˆœ๋ฒˆ โ€” order_index ์œ ์ง€(JPQL rank ๋น„๊ต ํ›„)
  • 4๋‹จ๊ณ„ โœ… ์ž๋™ ์Šน์ธ โ€” ๋‹จ์ผ @Transactional + flush ์ˆœ์„œ ์‹ค์ธก

๋ฐœํ–‰ SQL ๋ฐœ์ทŒ (์˜ˆ์ธก-์‹ค์ œ ๊ฐญ์ด ๊ฐ€์žฅ ํฐ ๊ฒƒ)

1) "EAGER๋ฉด ํ•ญ์ƒ join"์˜ ๋ฐ˜๋ก€ โ€” findById๋Š” join fetch, findByName(ํŒŒ์ƒ)์€ N+1

  • @EntityGraph ์ „: mine ๊ฒฝ๋กœ ์˜ˆ์•ฝ2+๋Œ€๊ธฐ2 = 8 SELECT(main + ์—ฐ๊ด€๋ณ„ ๋ณด์กฐ)
  • @EntityGraph ํ›„: ๋‹จ์ผ INNER JOIN = 2 SELECT

2) ์ž๋™ ์Šน์ธ flush ์ˆœ์„œ โ€” ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ๋ช…์‹œ flush๊ฐ€ delete-before-insert๋ฅผ ๋งŒ๋“ ๋‹ค
DELETE reservation โ†’ INSERT reservation โ†’ DELETE waiting โ†’ UPDATE waiting
flush ์—†์œผ๋ฉด ์•ก์…˜ํ(INSERTโ†’UPDATEโ†’DELETE)๊ฐ€ ์žฌ์ •๋ ฌ โ†’ ๊ฐ™์€ ์Šฌ๋กฏ UNIQUE ์ถฉ๋Œ.

๋ง์„ค์ธ ๊ฒฐ์ • 1~2๊ฐœ

  • order_index ์œ ์ง€ vs JPQL rank: ๋‘˜ ๋‹ค ์‹คํ–‰ ํ›„ ์œ ์ง€(UNIQUE ๋ฐฉ์–ด์„ ยท์Šน๊ธ‰ ํ†ตํ•ฉ). JPQL rank๋Š” ๋ฏธ์…˜ ํ›„ ๋ถ€๊ธฐ ์ œ๊ฑฐ ๋ฆฌํŒฉํ„ฐ๋ง 1์ˆœ์œ„.
  • fetch ์ „๋žต: EAGER ์œ ์ง€ํ•˜๋‹ค LAZY+@EntityGraph๋กœ ์ „ํ™˜(๊ธฐ๋ณธ๊ฐ’โ†’๋ฌธ์ œโ†’ํ•ด๊ฒฐ).

ํ”๋“ค๋ฆฐ ํ•œ ์žฅ๋ฉด

LAZY ์ „ํ™˜ ์งํ›„ ReservationServiceTest 3๊ฑด์ด LazyInitializationException โ€” 1-3 โ‘ฅ์—์„œ "reads tx-free + LAZY๋ฉด ํ„ฐ์ง„๋‹ค"๊ณ  ๋ฏธ๋ฆฌ ์ ์–ด๋‘” ์ขŒํ‘œ๊ฐ€ ์‹ค์ œ read ๊ฒฝ๋กœ์—์„œ ๊ทธ๋Œ€๋กœ ํ„ฐ์กŒ๊ณ , @EntityGraph๋กœ ๋‹ซ์•˜๋‹ค. OSIV๊ฐ€ admin ๊ฒฝ๋กœ๋งŒ ๊ฐ€๋ ค์ค€ ๊ฒƒ๋„ ๊ทธ๋•Œ ๋“œ๋Ÿฌ๋‚จ.

๋ช…์‹œ์  ๋น„๊ฒฐ์ • (Level 3 ๋ฒ”์œ„)

๋™์‹œ์„ฑยท๋ฝ(๋™์‹œ ์ทจ์†Œยท์Šน๊ธ‰์˜ ์žฌ๋ฒˆํ˜ธ ๊ฒฝ์Ÿยท์ค‘๋ณต ์Šน๊ธ‰)์€ ์ด๋ฒˆ ์‚ฌ์ดํด(JPA ์ „ํ™˜) ๋ฒ”์œ„ ๋ฐ– โ€” ๋ ˆ์ด์Šค ์žฌํ˜„โ†’๋น„๊ด€์  ๋ฝโ†’๊ฒฉ๋ฆฌ ๋น„๊ต ์•„ํฌ๋กœ ๋ถ„๋ฆฌ. ํ˜„์žฌ๋Š” UNIQUE ์ œ์•ฝ์ด ์ตœ์•…(์ค‘๋ณต ์˜ˆ์•ฝ)๋งŒ ๋ฐฉ์–ด.

softmoca and others added 30 commits June 3, 2026 18:16
FutureOnlyPolicy์™€ RecentWeekPopularPolicy๊ฐ€ ์ƒ์„ฑ์ž์—์„œ
Clock.systemDefaultZone()์„ ์ง์ ‘ ๋งŒ๋“œ๋Š” ์ˆจ์€ ์˜์กด์„ฑ์„ ๊ฐ–๊ณ  ์žˆ์–ด,
ํ…Œ์ŠคํŠธ์—์„œ ์‹œ๊ณ„๋ฅผ ๊ณ ์ •ํ•˜๋ ค๋ฉด ์šด์˜๊ณผ ๊ทœ์น™์ด ๊ฐ™์€ ๋ฏธ๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ๋”ฐ๋กœ ๋‘ฌ์•ผ ํ–ˆ๋‹ค.
์ด ์šฐํšŒ ๋•Œ๋ฌธ์— ๋ฏธ๋Ÿฌ๊ฐ€ ์„ธ ๊ณณ์— ์ค‘๋ณต๋๋‹ค
(FixedClockConfig.FixedClockPolicy, TestRecentWeekPopularPolicy,FutureOnlyPolicyTest.TestablePolicy).

๋‘ ์šด์˜ ์ •์ฑ…์ด Clock์„ ์ƒ์„ฑ์ž๋กœ ์ฃผ์ž…๋ฐ›๊ฒŒ ๋ฐ”๊พธ๊ณ  ์šด์˜ Clock ๋นˆ์„์ถ”๊ฐ€ํ–ˆ๋‹ค.
ํ…Œ์ŠคํŠธ ๊ณ ์ • ์‹œ๊ณ„ ์„ค์ •์€ ๋ฏธ๋Ÿฌ ๋Œ€์‹  ์šด์˜ ํด๋ž˜์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ์ƒ์„ฑ(new FutureOnlyPolicy(fixed))ํ•ด @primary๋กœ ์˜ฌ๋ฆฐ๋‹ค.
์‹œ๊ณ„ ๊ณ ์ •์„ ์ •์ฑ… ๋นˆ๋‹จ์œ„๋กœ ์บก์Аํ™”ํ•˜๋ฏ€๋กœ, FixedClockConfig(05-13)์™€ FixedPopularPolicyConfig(05-09)๊ฐ€ ๊ฐ๊ฐ @primary Clock์„ ๋“ฑ๋กํ•  ๋•Œ
์ƒ๊ธฐ๋Š” ์ถฉ๋Œ๋„ ๊ตฌ์กฐ์ ์œผ๋กœ ์—†๋‹ค.
๊ฒฐ๊ณผ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฒ€์ฆํ•˜๋Š” ๊ทœ์น™์ด ์šด์˜์ด ์‹ค์ œ ์“ฐ๋Š” ๊ทœ์น™๊ณผ ๊ฐ™์•„์ง€๊ณ , ๋ฏธ๋Ÿฌ ์•ฝ 3๊ฐœ๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.

์šด์˜:
- FutureOnlyPolicy: ์ƒ์„ฑ์ž์—์„œ Clock ์ฃผ์ž…๋ฐ›๋„๋ก ๋ณ€๊ฒฝ
- RecentWeekPopularPolicy: ์ƒ์„ฑ์ž์—์„œ Clock ์ฃผ์ž…๋ฐ›๋„๋ก ๋ณ€๊ฒฝ
- ClockConfig ์ถ”๊ฐ€: ์šด์˜ Clock ๋นˆ(systemDefaultZone) ๋“ฑ๋ก

ํ…Œ์ŠคํŠธ:
- FixedClockConfig: ๋ฏธ๋Ÿฌ FixedClockPolicy ์ œ๊ฑฐ, new FutureOnlyPolicy(fixed) ์‚ฌ์šฉ
- FixedPopularPolicyConfig: new RecentWeekPopularPolicy(fixed) ์‚ฌ์šฉ
- TestRecentWeekPopularPolicy ์‚ญ์ œ
- FutureOnlyPolicyTest: ๋ฏธ๋Ÿฌ TestablePolicy ์ œ๊ฑฐ, ์šด์˜ FutureOnlyPolicy ์ง์ ‘ ๊ฒ€์ฆ
"์˜ˆ์•ฝ ์‹œ์ ์ด ์–ด๋–ค ๊ธฐ์ค€ ์‹œ๊ฐ๋ณด๋‹ค ์ดํ›„์ธ๊ฐ€"๋ผ๋Š” ๋น„๊ต๋Š” ์ง€๊ธˆ๊นŒ์ง€ ์ •์ฑ…
(FutureOnlyPolicy) ๋‚ด๋ถ€์— !date.atTime(time).isAfter(now) ํ˜•ํƒœ๋กœ ์ˆจ์–ด
์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋น„๊ต๋Š” ์™ธ๋ถ€ ์ƒํƒœยท์‹œ๊ณ„์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ์ž…๋ ฅ(๋‚ ์งœยท์‹œ๊ฐ„)๊ณผ
๊ธฐ์ค€ ์‹œ๊ฐ๋งŒ์œผ๋กœ ๊ฒฐ์ •๋˜๋Š” ์ˆœ์ˆ˜ํ•œ ์‚ฌ์‹ค์ด๋ฏ€๋กœ, ๋„๋ฉ”์ธ์ด ์ž๊ธฐ ์ฑ…์ž„์œผ๋กœ ๊ฐ€์ ธ์•ผ
ํ•œ๋‹ค.

๋‚ ์งœ+์‹œ์ž‘์‹œ๊ฐ์„ ๋ฌถ์€ ๊ฐ’ ๊ฐ์ฒด ReservationDateTime์„ ๋„์ž…ํ•˜๊ณ , ์ˆœ์ˆ˜ ๋น„๊ต
startsAfter(moment)๋ฅผ ๋ถ€์—ฌํ•œ๋‹ค. ์˜๋„์ ์œผ๋กœ Clock์„ ๊ฐ–์ง€ ์•Š๋Š”๋‹ค โ€” ๊ธฐ์ค€
์‹œ๊ฐ์„ ์ธ์ž๋กœ ๋ฐ›๋Š” ๋น„๊ต๋ผ์•ผ ์‹œ๊ณ„ ์—†์ด ๊ฒฐ์ •์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋˜๊ณ , ๋„๋ฉ”์ธ์ด
"์ง€๊ธˆ์ด ๋ช‡ ์‹œ์ธ์ง€"๋ผ๋Š” ํ™˜๊ฒฝ ์˜์กด์„ฑ์„ ๋‹ค์‹œ ๋– ์•ˆ์ง€ ์•Š๋Š”๋‹ค.

๊ฒฝ๊ณ„ ๊ทœ์น™: ๊ฐ™์€ ์ˆœ๊ฐ„์€ '์ดํ›„'๊ฐ€ ์•„๋‹ˆ๋‹ค(false). "๋™์ผ ์‹œ๊ฐ์„ ํ—ˆ์šฉํ• ์ง€
๊ฑฐ๋ถ€ํ• ์ง€"๋Š” ์ด ๋น„๊ต ์œ„์—์„œ ์ •์ฑ…์ด ๋‚ด๋ฆฌ๋Š” ํŒ์ •์œผ๋กœ ๋‚จ๊ธด๋‹ค.

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” DBยท์Šคํ”„๋งยท์ •์ฑ… ์—†์ด ๋‹ค์„ฏ ๊ฒฝ๊ณ„(์ด์ „ ๋‚ ์งœ/๊ฐ™์€ ๋‚  ์ด์ „ ์‹œ๊ฐ/๋™์ผ ์ˆœ๊ฐ„/๊ฐ™์€ ๋‚ 
์ดํ›„ ์‹œ๊ฐ/์ดํ›„ ๋‚ ์งœ)๋ฅผ ์ž…๋ ฅ๋งŒ์œผ๋กœ ๊ฒ€์ฆํ•œ๋‹ค.
๊ธฐ์กด PolicyCollaboration์€ RecordingPolicy๋กœ "policy.validateCreatable์„
ํ˜ธ์ถœํ–ˆ๋Š”๊ฐ€"(creatableCalled)๋ฅผ ๋‹จ์–ธํ–ˆ๋‹ค. ์ด๋Š” Mockito๋ฅผ ์•ˆ ์ผ์„ ๋ฟ ๋ณธ์งˆ์ ์œผ๋กœ
ํ˜ธ์ถœ์„ ๋ณด๋Š” ํ–‰์œ„ ๊ฒ€์ฆ์ด๋ผ ๋Ÿฐ๋˜ํŒŒ์— ๊ฐ€๊น๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๋‹จ์œ„์˜ ๊ธฐ์ค€์œผ๋กœ ์‚ผ๋Š” ๊ณ ์ „ํŒŒ๋Š”
"์™ธ๋ถ€ ์˜์กด(DBยทHTTPยท์‹œ๊ณ„)์„ ๊ฒฉ๋ฆฌํ•˜๋˜ ํ˜‘๋ ฅ์ž๋Š” ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ์“ฐ๊ณ , ๊ฒฐ๊ณผ(์ƒํƒœ)๋ฅผ
๋ณธ๋‹ค". FutureOnlyPolicy๋Š” ํ˜‘๋ ฅ์ž์ด์ง€ ์™ธ๋ถ€ ์˜์กด์ด ์•„๋‹ˆ๊ณ , Clock ์ฃผ์ž…์œผ๋กœ ์‹œ๊ฐ„
๊ฒฐ์ •์„ฑ์„ ํ™•๋ณดํ–ˆ์œผ๋ฏ€๋กœ ์ถฉ๋ถ„ํžˆ ๊ฒฉ๋ฆฌ๋œ ์‹ค์ œ ๊ฐ์ฒด๋กœ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ˆ˜๋™ ์Šคํ… RecordingPolicyยทRejectingPolicy๋ฅผ ์‚ญ์ œํ•˜๊ณ , ๊ณ ์ • Clock์„
์ฃผ์ž…ํ•œ ์‹ค์ œ FutureOnlyPolicy๋กœ "๊ฑฐ๋ถ€ ์‹œ์ ์ด๋ฉด ์ƒ์„ฑ์ด ์‹คํŒจํ•˜๊ณ , ํ—ˆ์šฉ ์‹œ์ ์ด๋ฉด
์ƒ์„ฑ๋œ๋‹ค"๋Š” ๊ฒฐ๊ณผ๋งŒ ๊ฒ€์ฆํ•œ๋‹ค. "์œ„์ž„ํ–ˆ๋Š”๊ฐ€"๋Š” ๊ตฌํ˜„ ๋””ํ…Œ์ผ์ด๋ฏ€๋กœ ๋” ์ด์ƒ ๋ณด์ง€ ์•Š๋Š”๋‹ค.

๋ฉ”์‹œ์ง€ยท๊ฒฝ๊ณ„๋Š” ์—ฌ๊ธฐ์„œ ๋‹จ์–ธํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ฐ™์€ ์ •์ฑ…์ด FutureOnlyPolicyTest์—๋„
์“ฐ์ด์ง€๋งŒ ๋ณดํ˜ธ ๋Œ€์ƒ์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค:
- FutureOnlyPolicyTest: ๊ทœ์น™์˜ ๊ฒฝ๊ณ„ยท์—ฐ์‚ฐ๋ณ„ ๋ฉ”์‹œ์ง€ ์ •ํ™•์„ฑ
- ์—ฌ๊ธฐ: ์ •์ฑ… ๊ฒฐ๊ณผ๊ฐ€ create์˜ ์„ฑ๊ณต/์‹คํŒจ๋กœ ์—ฐ๊ฒฐ๋˜๋Š”๊ฐ€ (์ •์ฑ… ํ˜ธ์ถœ ๋ˆ„๋ฝ ํšŒ๊ท€ ๋ฐฉ์ง€)
๋ณดํ˜ธ ๋Œ€์ƒ์ด ๋‹ฌ๋ผ ๋‘˜ ๋‹ค ํ•„์š”ํ•˜๋ฉฐ, 5๊ฐœ ๊ฒฝ๊ณ„๋ฅผ ์—ฌ๊ธฐ์„œ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ค‘๋ณต๋„ ์•„๋‹ˆ๋‹ค.
ReservationPolicy์˜ ๋„ค ๋ฉ”์„œ๋“œ๊ฐ€ (LocalDate, LocalTime) ์›์‹œ๊ฐ’ ๋Œ€์‹ 
ReservationDateTime์„ ๋ฐ›๋„๋ก ๋ฐ”๊พผ๋‹ค. ์ด์ œ ์ฑ…์ž„์ด ๋˜๋ ท์ด ๊ฐˆ๋ฆฐ๋‹ค:
- ๋„๋ฉ”์ธ(ReservationDateTime): "๋‚ด ์‹œ์ ์ด ๊ธฐ์ค€ ์‹œ๊ฐ๋ณด๋‹ค ์ดํ›„์ธ๊ฐ€"๋ผ๋Š” ์‚ฌ์‹ค
- ์ •์ฑ…(FutureOnlyPolicy): "์ง€๊ธˆ"(Clock)์„ ๊ณต๊ธ‰ํ•˜๊ณ , ์ดํ›„๊ฐ€ ์•„๋‹ˆ๋ฉด ์œ„๋ฐ˜์œผ๋กœ
  ํŒ์ •ํ•˜๋ฉฐ ์—ฐ์‚ฐ๋ณ„ ๋ฉ”์‹œ์ง€๋ฅผ ์ž…ํžˆ๋Š” ์กฐํ•ฉ์ž

๋„ค ํ˜ธ์ถœ ์ง€์ ์ด ํ•œ ํƒ€์ž…์œผ๋กœ ํ†ต์ผ๋œ๋‹ค. ํŠนํžˆ ์ƒ์„ฑยท๋ณ€๊ฒฝ๋Œ€์ƒ์€ ์•„์ง Reservation
๊ฐ์ฒด๊ฐ€ ์—†๋Š” "์˜ˆ์ • ์Šฌ๋กฏ"์ด๋ผ ๋„๋ฉ”์ธ ๊ฐ์ฒด์— ํŒ๋‹จ์„ ๋งก๊ธธ ์ˆ˜ ์—†์—ˆ๋Š”๋ฐ, ๊ฐ’ ๊ฐ์ฒด๋ฅผ
(date, time)์œผ๋กœ ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ต์ผ๋œ๋‹ค. ์ทจ์†Œยท๋ณ€๊ฒฝ์€ ๊ธฐ์กด
๊ฐ์ฒด์˜ reservation.scheduledAt()์„ ๋„˜๊ธด๋‹ค. Reservation.create๋Š” ๊ฐ’ ๊ฐ์ฒด๋ฅผ
๋งŒ๋“ค์–ด ๊ฒ€์ฆํ•œ ๋’ค ์ƒ์„ฑํ•˜๋ฏ€๋กœ "๊ฒ€์ฆ ํ›„ ์ƒ์„ฑ" ์ˆœ์„œ๊ฐ€ ์œ ์ง€๋œ๋‹ค.

๋ถ€์ˆ˜ ๋ฐœ๊ฒฌ(๋ฏธ๋Ÿฌ๊ฐ€ ๊ฐ€๋ ค์˜จ ๊ฒฐํ•จ): ์šด์˜ ๋ณ€๊ฒฝ๋Œ€์ƒ ๋ฉ”์‹œ์ง€๋Š” "์ง€๋‚˜๊ฐ„ ๋‚ ์งœ, ์‹œ๊ฐ„..."
(์‰ผํ‘œ)์ธ๋ฐ ํ…Œ์ŠคํŠธ ๋ฏธ๋Ÿฌ๋Š” "์ง€๋‚˜๊ฐ„ ๋‚ ์งœยท์‹œ๊ฐ„..."(๊ฐ€์šด๋Žƒ์ )์œผ๋กœ ๋‹ฌ๋ž๋‹ค. Clock
์ฃผ์ž…์œผ๋กœ ์šด์˜ ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ๊ฒ€์ฆํ•˜๊ฒŒ ๋˜์ž ์ด ๋“œ๋ฆฌํ”„ํŠธ๊ฐ€ ๋“œ๋Ÿฌ๋‚ฌ๋‹ค. ์šด์˜
๋ฉ”์‹œ์ง€(์‰ผํ‘œ)๋กœ ํ†ต์ผํ•œ๋‹ค. ์ด๊ฒƒ์ด ๋ฏธ๋Ÿฌ ์ œ๊ฑฐ์˜ ์‹ค์งˆ ์ด๋“ โ€” ํ…Œ์ŠคํŠธ๊ฐ€ ์šด์˜์ด ์‹ค์ œ
์“ฐ๋Š” ์ฝ”๋“œยท๋ฉ”์‹œ์ง€๋ฅผ ๊ฒ€์ฆํ•˜๊ฒŒ ๋œ๋‹ค.

ํ…Œ์ŠคํŠธ ์žฌ๋ถ„๋ฐฐ ๋ฐ ๊ตฌ์„ฑ:
- ์‹œ์  ๋น„๊ต์˜ ๋‹ค์„ฏ ๊ฒฝ๊ณ„ โ†’ ReservationDateTimeTest๋กœ ์ด๋™(์ˆœ์ˆ˜, ๊ฐ€์žฅ ๋น ๋ฆ„)
- FutureOnlyPolicyTest๋Š” ํŒ์ •๋งŒ ๊ฒ€์ฆํ•˜๊ณ , "๋ฌด์—‡์„ ๋ณดํ˜ธํ•˜๋А๋ƒ"๋กœ ๋ฌถ๋Š”๋‹ค:
  ยท RejectionMessages โ€” ์ƒ์„ฑ/์ทจ์†Œ/๋ณ€๊ฒฝ/๋ณ€๊ฒฝ๋Œ€์ƒ์˜ ์—ฐ์‚ฐ๋ณ„ ๋ฉ”์‹œ์ง€(์—ฐ์‚ฐ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฒƒ์„
    ๋Œ€๋“ฑํ•˜๊ฒŒ)
  ยท AllowAndBoundary โ€” ๋ฏธ๋ž˜ ํ—ˆ์šฉ + '์ •ํ™•ํžˆ ์ง€๊ธˆ' ๊ฑฐ๋ถ€(๋„ค ์—ฐ์‚ฐ์ด ๊ณต์œ ํ•˜๋ฏ€๋กœ ๋Œ€ํ‘œ ํ•˜๋‚˜๋กœ)
  ์—ฐ์‚ฐ๋ณ„ nest๋กœ ์ชผ๊ฐœ์ง€ ์•Š๋Š”๋‹ค. ํ—ˆ์šฉยท๊ฒฝ๊ณ„๋Š” ์ƒ์„ฑ ์ „์šฉ์ด ์•„๋‹ˆ๋ผ ๊ณต์œ  ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๊ณ ,
  ๋‹จ์ผ ํ…Œ์ŠคํŠธ nest๋Š” ๋ฌถ์„ ๊ฒƒ ์—†์ด ๊ป๋ฐ๊ธฐ๋งŒ ๋Š˜๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์šด์˜ ๋™์ž‘์€ ๋™์ผํ•˜๋‹ค(๊ฐ™์€ ์ž…๋ ฅ์— ๊ฐ™์€ ํŒ์ •ยท๋ฉ”์‹œ์ง€). ๊ตฌ์กฐ์™€ ์ฑ…์ž„ ์œ„์น˜๋งŒ ๋ฐ”๋€๋‹ค.
โ€ฆ๋กœ ๋‹ค๋“ฌ๊ธฐ

JdbcWaitingRepositoryTest๋งŒ ๋‹ค๋ฅธ ์„ธ ์Šฌ๋ผ์ด์Šค(Reservation/Theme/ReservationTime)์™€
๋‹ฌ๋ฆฌ ๋‹จ์ˆœ CRUD๊นŒ์ง€ ๊ฒ€์ฆํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. save ๋‹จ๊ฑด, deleteByIdยทfindById ๋‹จ๊ฑด์„ ๋ณ„๋„
ํ…Œ์ŠคํŠธ๋กœ ๋“ค๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์ด๋Š” "์ง์ ‘ ์ž‘์„ฑํ•œ ๋น„๋‹จ์ˆœ SQL๋งŒ ๋ณธ๋‹ค / saveยทfindById์˜ ํ•ดํ”ผ
ํŒจ์Šค๋Š” ์„œ๋น„์Šค ํ†ตํ•ฉยท์ธ์ˆ˜์—์„œ ๊ฑฐ์ณ๊ฐ€๋‹ˆ ๋‘๊ป๊ฒŒ ๋ณด์ง€ ์•Š๋Š”๋‹ค"๋Š” ๋‚˜๋จธ์ง€ ์Šฌ๋ผ์ด์Šค์˜ ์›์น™๊ณผ
์–ด๊ธ‹๋‚œ๋‹ค

๋ณ€๊ฒฝ:
- save ๋‹จ๊ฑด ํ…Œ์ŠคํŠธ ์ œ๊ฑฐ โ€” ํ‰๋ฒ”ํ•œ INSERT์˜ ํ•ดํ”ผ ํŒจ์Šค๋Š” WaitingService ํ†ตํ•ฉยท์ธ์ˆ˜
  ํ๋ฆ„(๋Œ€๊ธฐ ์‹ ์ฒญ โ†’ ๋‚ด ๋ชฉ๋ก ์กฐํšŒ)์ด ๋ผ์šด๋“œํŠธ๋ฆฝํ•œ๋‹ค. order_index ์ปฌ๋Ÿผ์ด ํ•˜๋‚˜ ๋”
  ์žˆ๋‹ค๋Š” ๊ฒƒ๋งŒ์œผ๋กœ 4์ปฌ๋Ÿผ reservation save(์ด๋ฏธ ์ œ์™ธ๋จ)์™€ ๋‹ค๋ฅด๊ฒŒ ์ทจ๊ธ‰ํ•  ๊ทผ๊ฑฐ๋Š” ์•ฝํ•˜๋‹ค.
- deleteByIdยทfindById ๋‹จ๊ฑด ํ…Œ์ŠคํŠธ ์ œ๊ฑฐ โ€” ์‚ญ์ œ๋Š” ReservationServiceTest์˜ ์ž๋™ ์Šน๊ฒฉ
  (existsWaiting=false)์—์„œ, ์กฐํšŒ๋Š” ์ˆœ๋ฒˆ ๊ฐฑ์‹  ๊ฒ€์ฆ์—์„œ ์ด๋ฏธ ๊ฑฐ์ณ๊ฐ„๋‹ค.
- updateOrderIndex ๊ฒ€์ฆ์€ ์œ ์ง€ โ€” reservation ์Šฌ๋ผ์ด์Šค๊ฐ€ updateDateAndTime์„ ๋‚จ๊ธด
  ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž๋ฆฌ. ์ง์ ‘ ์ž‘์„ฑํ•œ ๋ถ€๋ถ„ UPDATE์˜ ์ปฌ๋Ÿผ๋ช…ยทSET/WHERE ์ •ํ™•์„ฑ์€ SQL์„ ์‹ค์ œ
  ์‹คํ–‰ํ•ด์•ผ๋งŒ ์žกํžˆ๊ณ , ๋Œ€๊ธฐ ์žฌ์ •๋ ฌ์˜ ํ•ต์‹ฌ์ด๋‹ค. (์—ฌ๊ธฐ์„œ findById๋Š” ๊ฒ€์ฆ ๋„๊ตฌ๋กœ๋งŒ ์“ด๋‹ค.)
- ๋‚จ์€ ๊ฒ€์ฆ: findBySlot(์ •๋ ฌ+ํ•„ํ„ฐ+์กฐ์ธ) / findByName(๋‹ค์ค‘์ •๋ ฌ+ํ•„ํ„ฐ) / updateOrderIndex.
- ํด๋ž˜์Šค javadoc์— ์ œ์™ธ ์›์น™๊ณผ "ํ•œ๋•Œ ๋‹จ๊ฑด๊นŒ์ง€ ๋“ค๊ณ  ์žˆ์—ˆ๋˜ ํ”์ "์„ ๊ธฐ๋กํ•ด, ๋„ค ์Šฌ๋ผ์ด์Šค๊ฐ€
  ๊ฐ™์€ ๊ธฐ์ค€์œผ๋กœ ์ฝํžˆ๋„๋ก ํ–ˆ๋‹ค.
Available ๋ธ”๋ก(findAvailable 3์ผ€์ด์Šค)์ด JdbcReservationTimeRepositoryTest์™€
๊ฐ™์€ SQL ํšŒ๊ท€(NOT IN ์ •ํ™•์„ฑยทํ…Œ๋งˆ ๊ฒฝ๊ณ„ยท๋นˆ ์ƒํƒœ)๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๋„ค์˜ค ํ”ผ๋“œ๋ฐฑ์˜
"๊ฒ€์ฆ ๊ฐ€์น˜ ร— ๋น„์šฉ" ๋‘ ์ถ•์œผ๋กœ ๋ณด๋ฉด, ๊ฐ™์€ ๊ฐ€์น˜๋ฅผ ์Šฌ๋ผ์ด์Šค๊ฐ€ ๋” ์‹ธ๊ฒŒ(@jdbcTest,
ํ†ฐ์บฃยทํ’€์ปจํ…์ŠคํŠธ ์—†์Œ) ๊ทธ๋ฆฌ๊ณ  ๋” ๋„“๊ฒŒ(์Šฌ๋ผ์ด์Šค๋Š” ๋‚ ์งœ ๊ฒฝ๊ณ„๊นŒ์ง€ ๋ณธ๋‹ค) ์‚ฌ๊ณ  ์žˆ์–ด,
์„œ๋น„์Šค ํ†ตํ•ฉ์€ ๊ฐ™์€ ํšŒ๊ท€๋ฅผ ๋‘ ๋ฒˆ์งธ๋กœ ๋น„์‹ธ๊ฒŒ ์‚ฌ๋Š” ์ž‰์—ฌ์˜€๋‹ค. ์„œ๋น„์Šค๊ฐ€ ์ถ”๊ฐ€๋กœ ์‚ฌ๋˜ ๊ฑด
DTO ๋งคํ•‘(stream.map().toList()) ํ•œ ์ค„๋ฟ์ด๊ณ , ์ด๊ฑด ํšŒ๊ท€ ์œ„ํ—˜์ด ๊ฑฐ์˜ ์—†๋‹ค.

ํ† ๋ก  ๊ทœ์น™ 2๋ฒˆ("๊ฐ™์€ ๊ฒƒ์„ ๋‘ ๋ฒˆ ๊ฒ€์ฆํ•˜์ง€ ์•Š๋Š”๋‹ค")๊ณผ 4๋ฒˆ("์•ˆ ํ•˜๋Š” ๊ฒƒ๋„ ๊ฒฐ์ •")์— ๋”ฐ๋ผ, SQL ํšŒ๊ท€๋Š” ์Šฌ๋ผ์ด์Šค์— ์ผ์ž„ํ•˜๊ณ 
์„œ๋น„์Šค์—์„œ ์ œ๊ฑฐํ•œ๋‹ค.

๋ณ€๊ฒฝ:
- Available @nested ๋ธ”๋ก ์ „์ฒด ์ œ๊ฑฐ (์˜ˆ์•ฝ๋œ_์‹œ๊ฐ„_์ œ์™ธ / ๋‹ค๋ฅธ_ํ…Œ๋งˆ๋Š”_๊ฐ€๋Šฅ /
  ์˜ˆ์•ฝ_์—†์œผ๋ฉด_์ „๋ถ€_๊ฐ€๋Šฅ). ๋™์ผยท์ƒ์œ„ ๊ฒ€์ฆ์ด JdbcReservationTimeRepositoryTest์— ์žˆ๋‹ค.
- ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ ๋œ import ์ •๋ฆฌ (java.util.List, ReservationTimeResult, assertThat).
- ํด๋ž˜์Šค javadoc ๊ฐฑ์‹ : ๋‚จ๋Š” ๊ฒ€์ฆ ๋Œ€์ƒ์„ "์‚ญ์ œ ๊ฑฐ๋ถ€ ์ •์ฑ…(์„œ๋น„์Šค๋งŒ์˜ ์˜ˆ์™ธ ์ „ํ™˜ ํ˜‘๋ ฅ)"์œผ๋กœ
  ๋ช…ํ™•ํžˆ ํ•˜๊ณ , findAvailableยทcreateยทfindAll์„ "์™œ ์ด ์ž๋ฆฌ์—์„œ ๊ฒ€์ฆํ•˜์ง€ ์•Š๋Š”์ง€"๋กœ ๊ธฐ๋ก.

๋‚จ๋Š” ๊ฒƒ โ€” DeletePolicy ๋ธ”๋ก๋งŒ ์œ ์ง€ํ•œ๋‹ค. existsByTimeId(boolean)๋ฅผ ๋ฐ›์•„ ์˜ˆ์™ธ๋กœ
์ „ํ™˜ํ•˜๋Š” ์˜์‚ฌ๊ฒฐ์ •์€ ์Šฌ๋ผ์ด์Šค๋กœ ๋‚ด๋ ค๊ฐˆ ์ˆ˜ ์—†๋Š” ์„œ๋น„์Šค ๊ณ ์œ ์˜ ํ˜‘๋ ฅ์ด๋ผ ์ด ์ž๋ฆฌ๋งŒ์˜
๊ฐ€์น˜๊ฐ€ ๋˜๋ ทํ•˜๋‹ค. ๋ธ”๋ก์ด ํ•˜๋‚˜๋งŒ ๋‚จ๋Š” ๊ฑด ์ด ์„œ๋น„์Šค๊ฐ€ ๊ทธ๋งŒํผ ๋‹จ์ˆœํ•˜๋‹ค๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด
๊ฒฐ๊ณผ์ด์ง€ ๊ฒ€์ฆ ๋ถ€์กฑ์ด ์•„๋‹ˆ๋‹ค. (createยทfindAll์€ ๋‹จ์ˆœ ์œ„์ž„์ด๋ผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์„œ๋น„์Šค ํ†ตํ•ฉ
๋Œ€์ƒ์ด ์•„๋‹ˆ์—ˆ๋‹ค.)
ReservationServiceTest๋Š” ์„œ๋น„์Šค๋งŒ์˜ ํ˜‘๋ ฅ(์˜ˆ์™ธ ์ „ํ™˜ยท๋‹ค์ค‘ ๋„๋ฉ”์ธ ํ˜‘๋ ฅยท๋ณ‘ํ•ฉ/์ •๋ ฌยท๋ถ„๊ธฐ)์„
๊ฒ€์ฆํ•˜๋Š” ๊ฐ€์น˜ ์žˆ๋Š” ์ž๋ฆฌ์ง€๋งŒ, ๋‘ ๊ฐ€์ง€ ๊ฒ€์ฆ ๋ชจ๋“œ๊ฐ€ ํ•œ ํŒŒ์ผ์— ์„ž์—ฌ ์žˆ์œผ๋ฉด์„œ ๊ทธ ๊ธฐ์ค€์ด
๋“œ๋Ÿฌ๋‚˜ ์žˆ์ง€ ์•Š์•„ "์™œ ์–ด๋””๋Š” ๋ถ„๊ธฐ๋ฅผ ๋‹ค ๋ณด๊ณ  ์–ด๋””๋Š” ํ•œ ์ผ€์ด์Šค๋งŒ ๋ณด๋Š”๊ฐ€"๊ฐ€ ํ๋ฆฟํ–ˆ๋‹ค.

๋‘ ๋ชจ๋“œ๋ฅผ ์˜์‹์ ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜๊ณ  ๊ฐ ๋ธ”๋กยทํ…Œ์ŠคํŠธ์— ํ‘œ์‹œํ•œ๋‹ค:
- ๋ชจ๋“œ A(๋ถ„๊ธฐ ์ „๋ถ€): ํšŒ๊ท€๊ฐ€ ์„œ๋น„์Šค ์•ˆ์˜ ๋กœ์ง์ด๋ผ ์Šฌ๋ผ์ด์Šค๋กœ ๋ชป ๋‚ด๋ ค์˜ค๊ณ  ๋ถ„๊ธฐ ๊ฒฐ๊ณผ๊ฐ€
  ์„œ๋กœ ์˜๋ฏธ ์žˆ๊ฒŒ ๋‹ค๋ฅผ ๋•Œ. CancelAndPromote, MyReservationsAndWaitings, Update,
  ๊ทธ๋ฆฌ๊ณ  Create์˜ ์‚ฌ์ „ ์กฐํšŒ ์‹คํŒจ ๋‘ ๋ถ„๊ธฐ.
- ๋ชจ๋“œ B(๋Œ€ํ‘œ ํ•œ ์ผ€์ด์Šค): ๊ฐ™์€ ํšŒ๊ท€๋ฅผ ์Šฌ๋ผ์ด์Šค๊ฐ€ ๋” ์‹ธ๊ฒŒ ์ด๋ฏธ ์‚ฌ์ฃผ๊ณ  ์„œ๋น„์Šค๋Š” "๊ทธ ๊ฒ€์ฆ์„
  ํ๋ฆ„์—์„œ ํ˜ธ์ถœํ•˜๋Š” ํ˜‘๋ ฅ"๋งŒ ํ•  ๋•Œ. Create์˜ ์ค‘๋ณต ๊ฒ€์‚ฌยท์ •์ฑ… ์—ฐ๊ฒฐ.

ํ•ต์‹ฌ์€ "๋น ์ง์—†์ด ๋‹ค ์ง ๋‹ค"๊ฐ€ ์•„๋‹ˆ๋ผ "๊ฐ ํšŒ๊ท€๋ฅผ ์–ด๋””์„œ ๊ฐ€์žฅ ์‹ธ๊ฒŒ ๋ณดํ˜ธํ• ์ง€ ์ •ํ•˜๊ณ  ๊ทธ ๊ฒฐ์ •์„
๋“œ๋Ÿฌ๋‚ธ๋‹ค"์ด๋‹ค. ๊ทธ ๊ด€์ ์—์„œ ์กฐ์ •ํ•œ ๊ฒƒ:

- ์กด์žฌํ•˜์ง€_์•Š๋Š”_์‹œ๊ฐ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€(๋ชจ๋“œ A). ๊ธฐ์กด์—” ์กด์žฌํ•˜์ง€_์•Š๋Š”_ํ…Œ๋งˆ๋งŒ ์žˆ์—ˆ๋‹ค. ์‹œ๊ฐ„ยทํ…Œ๋งˆ๋Š”
  ์„œ๋กœ ๋‹ค๋ฅธ Repository ์กฐํšŒ์ด๊ณ  ์„œ๋กœ ๋‹ค๋ฅธ ๋ฉ”์‹œ์ง€("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค." /
  "...ํ…Œ๋งˆ์ž…๋‹ˆ๋‹ค.")๋ฅผ ์ฃผ๋Š” ๋ณ„๊ฐœ ๋ถ„๊ธฐ๋ผ, ํ•œ์ชฝ๋งŒ ๋‘๋ฉด ๋‹ค๋ฅธ ์ชฝ ํšŒ๊ท€๊ฐ€ ์ƒŒ๋‹ค. ๋ถ„๊ธฐ ๊ฒฐ๊ณผ๊ฐ€
  ๋‹ค๋ฅด๋ฏ€๋กœ ๋ชจ๋“œ A๋กœ ๋‘˜ ๋‹ค ๋ช…์‹œํ•œ๋‹ค.
- ์ •์ƒ_์ƒ์„ฑ โ†’ ๋ฏธ๋ž˜_์˜ˆ์•ฝ_์ƒ์„ฑ์œผ๋กœ ์˜๋„ ๋ช…ํ™•ํ™”(๋ชจ๋“œ Bยท์ •์ฑ… ํ†ต๊ณผ ์ธก). ๊ธฐ์กด์—” doesNotThrow๋งŒ
  ๋ด์„œ "์ •์ฑ…์ด ๋ฏธ๋ž˜๋ฅผ ํ†ต๊ณผ์‹œํ‚จ๋‹ค"๋Š” ์˜๋„๊ฐ€ ์ด๋ฆ„ยท๋‹จ์–ธ์— ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š์•˜๋‹ค. ๋ฐ˜ํ™˜ ๊ฒฐ๊ณผ์˜
  ๋‚ ์งœยท์‹œ๊ฐ„์„ ๋‹จ์–ธํ•ด ํ•ดํ”ผํŒจ์Šค ์‚ฐ์ถœ๋ฌผ์„ ํ™•์ธํ•˜๊ณ , ๊ฑฐ๋ถ€ ์ธก(๊ณผ๊ฑฐ_๊ฑฐ๋ถ€)๊ณผ ์ง์ง€์–ด ์ •์ฑ… ์—ฐ๊ฒฐ์˜
  ์–‘๋ฉด์„ ๋˜๋ ท์ด ํ•œ๋‹ค.
- ๋‹ค๋ฅธ_ํ…Œ๋งˆ_ํ—ˆ์šฉ์— "์™œ ํ…Œ๋งˆ ์ถ• ํ•œ ์ผ€์ด์Šค๋กœ ์ถฉ๋ถ„ํ•œ๊ฐ€"(์„ธ ์ถ• ์ •ํ™•์„ฑ์€ ์Šฌ๋ผ์ด์Šค๊ฐ€, ์—ฌ๊ธฐ์„  ํ˜ธ์ถœ
  ํ˜‘๋ ฅ๋งŒ ๋Œ€ํ‘œ ํ™•์ธ)๋ฅผ ์ฃผ์„์œผ๋กœ ๋ช…์‹œ. ๋‚ ์งœยท์‹œ๊ฐ„ ์ถ•์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์€ ์˜์‹์  ์ƒ๋žต์ด๋‹ค.
- ํด๋ž˜์Šค javadoc์— ๋‘ ๋ชจ๋“œ์˜ ์ •์˜์™€ ์„ ํƒ ๊ธฐ์ค€์„ ๋ฐ•์•„, ๋‹ค์Œ์— ์ฝ๋Š” ์‚ฌ๋žŒ์ด ๊ฐ ์ž๋ฆฌ์˜ ๋ถ„๋‹ด์„
  ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๊ฐœ์ˆ˜๋ฅผ ๋Š˜๋ฆฌ๋Š” ๊ฒŒ ๋ชฉ์ ์ด ์•„๋‹ˆ๋‹ค(์‹œ๊ฐ„ ๋ถ„๊ธฐ ํ•˜๋‚˜๋งŒ ๋Š˜์—ˆ๋‹ค). ์ด๋ฏธ ์žˆ๋˜ ๊ฒฐ์ •๋“ค์„ ์˜์‹์ 
๊ฒฐ์ •์œผ๋กœ ๋“œ๋Ÿฌ๋‚ด๊ณ , ์ผ๊ด€์„ฑ์ด ๋น„์–ด ์žˆ๋˜ ํ•œ ์นธ์„ ์ฑ„์šด ๊ฒƒ์ด๋‹ค.
์‚ฌ์ดํด 1์—์„œ WaitingService.create์˜ ๋ถ„๊ธฐ๋“ค์„ ํ†ตํ•ฉ(WaitingServiceTest)๊ณผ Mock ๋‹จ์œ„
(WaitingServiceMockTest) ๋‘ ๋ฐฉ์‹์œผ๋กœ ์งœ์„œ ๋น„๊ตํ–ˆ๋‹ค. ์ด์ œ ๊ทธ ๋น„๊ต๋ฅผ ๋๋‚ด๊ณ  ํ•œ์ชฝ์„
๋ฉ”์ธ์œผ๋กœ ํ™•์ •ํ•œ๋‹ค โ€” ๋‘˜ ๋‹ค ์˜๊ตฌํžˆ ํ™œ์„ฑ์œผ๋กœ ๋‘๋ฉด ๊ฐ™์€ ํšŒ๊ท€๋ฅผ ๋‘ ์ž๋ฆฌ์—์„œ ์‚ฌ๋Š” ๋น„์šฉ
์ค‘๋ณต(ํ† ๋ก  ๊ทœ์น™ 2)์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ฒฐ๋ก : (B) ํ†ตํ•ฉ์„ ๋ฉ”์ธ์œผ๋กœ ๋‘”๋‹ค. ๋„ค ์ถ•์œผ๋กœ ํŒ๋‹จํ–ˆ๋‹ค.
- ์…‹์—… ๋น„์šฉ: ๋ถ„๊ธฐ 5๊ฐœ๋Š” ์…‹์—… ํญ๋ฐœ ์ง์ „์ด์ง€๋งŒ ์•„์ง ํญ๋ฐœ์€ ์•„๋‹ˆ๊ณ , fixture๊ฐ€ ํ•œ ์ค„
  ์…‹์—…์ด๋ผ ํ†ตํ•ฉ ์ค€๋น„ ๋น„์šฉ์ด ์ž‘๋‹ค.
- ์‹คํ–‰ ๋น„์šฉ: Mock์ด ๊ฐ€๋ณ์ง€๋งŒ ์‚ฌ์ดํด 1 ๊ทœ๋ชจ์—์„  ๊ฒฐ์ •์  ๋‹จ์ ์ด ์•„๋‹ˆ๋‹ค.
- ํšŒ๊ท€ ์•ˆ์ „์„ฑ: ํ†ตํ•ฉ์€ ๋ถ„๊ธฐ + SQL + ๋นˆ ํ˜‘๋ ฅ์„ ํ•œ ๋ฒˆ์— ๋ณด์žฅํ•œ๋‹ค. Mock์œผ๋กœ ๊ฐ€๋ฉด SQL์€
  "๋ฆฌํ„ดํ•œ๋‹ค ์น˜๊ณ "๋ผ ์Šฌ๋ผ์ด์Šค ์ง์ง“๊ธฐ๊ฐ€ ํ•„์ˆ˜๋‹ค(์Šฌ๋ผ์ด์Šค๋Š” ์ด๋ฏธ ์žˆ์–ด Mock๋„ ์ •๋‹นํ™”๋Š” ๋จ).
- ์ผ๊ด€์„ฑ: ๋‹ค๋ฅธ ์„ธ ์„œ๋น„์Šค๊ฐ€ ๋ชจ๋‘ ํ†ตํ•ฉ์ด๋ผ, ์—ฌ๊ธฐ๋งŒ Mock์ด๋ฉด ๊ฒ€์ฆ ์Šคํƒ€์ผ์ด ๋น„๋Œ€์นญ์ด ๋œ๋‹ค.
๋ถ„๊ธฐ๊ฐ€ ํญ๋ฐœํ•˜์ง€ ์•Š๊ณ  fixture๊ฐ€ ๊ฐ€๋ฒผ์šฐ๋ฉฐ ์ผ๊ด€์„ฑ ๊ฐ€์น˜๊ฐ€ ํฐ ํ˜„์žฌ ์ƒํƒœ์—์„  ํ†ตํ•ฉ์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.

๋ณ€๊ฒฝ:
- WaitingServiceMockTest์— @disabled๋ฅผ ๋ถ™์—ฌ ํ•™์Šต ๊ธฐ๋ก์œผ๋กœ parkํ•œ๋‹ค. ์˜๊ตฌ ์‚ญ์ œํ•˜๊ธฐ์—”
  "Mock vs ํ†ตํ•ฉ"์„ ์ฝ”๋“œ๋กœ ๋น„๊ตํ•œ ํ•™์Šต ๊ฐ€์น˜๊ฐ€ ์ปค์„œ ๋ณด์กดํ•˜๋˜, ํ™œ์„ฑ ๊ฒ€์ฆ์—์„œ ๋นผ ๋น„์šฉ ์ค‘๋ณต์„
  ์—†์•ค๋‹ค. ์ปดํŒŒ์ผ์€ ๊ณ„์† ๊ฒ€์‚ฌ๋˜๋ฏ€๋กœ ์šด์˜ API๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋“œ๋Ÿฌ๋‚œ๋‹ค.
- WaitingServiceMockTest ์ฃผ์„ ํ™•์žฅ: ๋น„๊ต 4์ถ•(์…‹์—…/์‹คํ–‰/ํšŒ๊ท€ ์•ˆ์ „์„ฑ ์ข…๋ฅ˜/ํ•€ํฌ์ธํŠธ vs
  ๋ฆฌํŒฉํ† ๋ง ์นœํ™”์„ฑ)๊ณผ ํŒ๋‹จ ๊ฐ€์ด๋“œ, create์— ์ ์šฉํ•œ ๊ฒฐ๋ก (B), "์‚ฌ์ดํด 2์—์„œ ๋‹ค์‹œ ๋ณธ๋‹ค"๋ฅผ ๊ธฐ๋ก.
  ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„๋Š” "๊ฒฐ๊ณผ๋กœ ์•ˆ ๋ณด์ด๋Š” ํ˜‘๋ ฅ"์˜ ์ „ํ˜•์ด๋ผ Mock ๊ฐ€์น˜๊ฐ€ ์‚ด์•„๋‚  ์ˆ˜ ์žˆ๋Š” ์ž๋ฆฌ๋ผ,
  @transactional์„ ๋„ฃ์œผ๋ฉฐ park๋ฅผ ํ’€์ง€ ๋‹ค์‹œ ํŒ๋‹จํ•œ๋‹ค.
- WaitingServiceTest ์ฃผ์„์„ "๋น„๊ต ๊ธฐ์ค€์„ "์—์„œ "๋ฉ”์ธ ๊ฒ€์ฆ + (B) ๊ฒฐ๋ก "์œผ๋กœ ๊ฐฑ์‹ .

ํ…Œ์ŠคํŠธ ๋™์ž‘ ๋ณ€ํ™”: WaitingServiceMockTest์˜ 5๊ฐœ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰์—์„œ ์ œ์™ธ๋œ๋‹ค(ํ†ตํ•ฉ์ด ๊ฐ™์€
๋ถ„๊ธฐ๋ฅผ ์ปค๋ฒ„). ํ†ตํ•ฉยท์Šฌ๋ผ์ด์Šคยท์ธ์ˆ˜๋Š” ๊ทธ๋Œ€๋กœ๋‹ค.
JdbcTemplate ๊ธฐ๋ฐ˜ ThemeยทReservationTime ์˜์†ํ™”๋ฅผ JPA๋กœ ์ „ํ™˜ํ•œ๋‹ค. ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š”
1-1 ๋Œ€์ƒ(๋…๋ฆฝ ํด๋ž˜์Šค) ๋‘˜์„ @entity๋กœ ์ง์ ‘ ๋งคํ•‘ํ•˜๊ณ , ํ—ฅ์‚ฌ๊ณ ๋‚  ํฌํŠธ๋Š” ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๋‘” ์ฑ„
JPA ์–ด๋Œ‘ํ„ฐ๋ฅผ ๋ผ์šด๋‹ค.

โ–  ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘ (domain/Theme, domain/ReservationTime)
- @entity + @id + @GeneratedValue(strategy = IDENTITY)
- final ์ œ๊ฑฐ + protected ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ์ถ”๊ฐ€(JPA ๋ฆฌํ”Œ๋ ‰์…˜ ์ง„์ž…๋กœ). ๋‹จ ์ •์  ํŒฉํ† ๋ฆฌ
  (create/withId)์™€ ์ƒ์„ฑ์ž ๊ฒ€์ฆ์€ ์œ ์ง€ โ†’ ๋ถˆ๋ณ€์„ ํ†ต์งธ๋กœ ๋ฒ„๋ฆฌ์ง€ ์•Š๊ณ  ์ง„์ž…๋กœ๋งŒ ์ถ”๊ฐ€.
- ์ปฌ๋Ÿผ๋ช…์€ CamelCaseToUnderscoresNamingStrategy ์— ์œ„์ž„(startAtโ†’start_at,
  thumbnailUrlโ†’thumbnail_url). @column ์€ ์ œ์•ฝ(nullable/length/unique)๋งŒ ๋ช…์‹œ.
- withId ๋Š” reservationยทwaiting ์˜ Jdbc RowMapper ๊ฐ€ ์•„์ง ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋ณด์กด.

โ–  ๋ฆฌํฌ์ง€ํ† ๋ฆฌ (adapter/persistence) โ€” ํฌํŠธ ์ˆœ์ˆ˜ ์œ ์ง€ + JPA ์–ด๋Œ‘ํ„ฐ
- ํฌํŠธ(ThemeRepositoryยทReservationTimeRepository)๋Š” Spring import 0 ์œผ๋กœ ์œ ์ง€.
- ThemeJpaRepositoryยทReservationTimeJpaRepository(Spring Data) ์‹ ์„ค โ†’ CRUD ์ž๋™.
- ThemeRepositoryAdapterยทReservationTimeRepositoryAdapter ๊ฐ€ ํฌํŠธ๋ฅผ ๊ตฌํ˜„, CRUD ๋Š” ์œ„์ž„.
- ๊ต์ฐจ ํ…Œ์ด๋ธ” ์ฟผ๋ฆฌ๋Š” ๊ณผ๋„๊ธฐ ์ฒ˜๋ฆฌ(์•„์ง reservation ์ด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹˜ โ†’ JPQL ๋ถˆ๊ฐ€):
  ยท findAvailable โ†’ native @query (๊ฒฐ๊ณผ๋Š” ReservationTime ์—”ํ‹ฐํ‹ฐ๋กœ ๋งคํ•‘)
  ยท findPopularBetween โ†’ ๊ธฐ์กด JdbcTemplate + POPULAR_ROW_MAPPER ์œ ์ง€
    (PopularThemeProjection ์ด ์ƒ์„ฑ์ž ๊ธฐ๋ฐ˜์ด๋ผ native ๋ถ€์ ํ•ฉ)
  1-2 ์—์„œ Reservation ์—”ํ‹ฐํ‹ฐํ™” ํ›„ ๋‘˜ ๋‹ค JPQL ๋กœ ์Šน๊ฒฉ ์˜ˆ์ •.
- ์„ค๊ณ„ ๋ณ€๊ฒฝ: ์ง์ „ ์•ˆ๋‚ด์˜ "ํฌํŠธ๋ฅผ JpaRepository ๋กœ ํ™•์žฅ" ๊ถŒ๊ณ ๋ฅผ ์ฒ ํšŒ. ๋ฏธ์—”ํ‹ฐํ‹ฐ
  reservation ์„ ์ฐธ์กฐํ•˜๋Š” ๋‘ ์ฟผ๋ฆฌ ๋•Œ๋ฌธ์— ํฌํŠธ ํ™•์žฅ ์‹œ native+projection ๊ผผ์ˆ˜๊ฐ€
  ๊ฐ•์ œ๋จ โ†’ ํฌํŠธ ์ˆœ์ˆ˜์„ฑ ๋ณด์กด์„ ์œ„ํ•ด ์–ด๋Œ‘ํ„ฐ ๋ฐฉ์‹ ์ฑ„ํƒ.
- ๊ตฌ Jdbc ๊ตฌํ˜„(JdbcThemeRepositoryยทJdbcReservationTimeRepository)์€ ํ›„์† ์ปค๋ฐ‹์—์„œ ์ œ๊ฑฐ.

โ–  ์„ค์ •
- build.gradle: spring-boot-starter-jdbc โ†’ spring-boot-starter-data-jpa
- application.properties(main): ddl-auto=none + show-sql + hibernate.format_sql
  + logging org.hibernate.SQL=DEBUG / org.hibernate.orm.jdbc.bind=TRACE
- application.properties(test): ddl-auto=none ์ถ”๊ฐ€

โ–  DDL ์†Œ์œ ๊ถŒ โ€” ddl-auto=none + schema.sql ์ „์ฒด ๊ถŒ์œ„ (๊ณผ๋„๊ธฐ, ์ค‘์š”)
- ์ฒ˜์Œ์—” themeยทreservation_time ์„ Hibernate(create-drop)๊ฐ€, reservationยทwaiting ์„
  schema.sql ์ด ์†Œ์œ ํ•˜๋„๋ก ์ชผ๊ฐฐ์œผ๋‚˜ โ€” Hibernate ๊ฐ€ ์—†๋Š” @jdbcTest ์Šฌ๋ผ์ด์Šค์™€
  schema.sql ์„ ์‹คํ–‰(@SpringBootTest, defer ๊ธฐ๋ณธ false)์—์„œ reservation ์˜ FK ๊ฐ€
  ์•„์ง ์—†๋Š” reservation_time ์„ ์ฐธ์กฐ โ†’ ScriptStatementFailedException(63๊ฐœ ์‹คํŒจ).
- ์›์ธ: DDL ์†Œ์œ ๊ถŒ ๋ถ„ํ• ์€ ๋น„-JPA ์Šฌ๋ผ์ด์Šค์™€ ์–‘๋ฆฝ ๋ถˆ๊ฐ€.
- ํ•ด๊ฒฐ: schema.sql ์„ 4๊ฐœ ํ…Œ์ด๋ธ” ์ „์ฒด ๊ถŒ์œ„๋กœ ๋ณต๊ตฌ + ddl-auto=none(mainยทtest ์–‘์ชฝ).
  4๊ฐœ ํ…Œ์ด๋ธ”์ด ๋ชจ๋‘ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋˜๋Š” 1-2 ์ข…๋ฃŒ ์‹œ create-drop ์œผ๋กœ ์ „ํ™˜ ์˜ˆ์ •.
- Hibernate ์ž๋™ ์ƒ์„ฑ DDL ์€ ๊ฒฉ๋ฆฌ ํ™˜๊ฒฝ(Boot 3.4.4/Hibernate 6/H2)์—์„œ ๊ด€์ฐฐํ•ด ๋ฌธ์„œํ™”.

โ–  ์‹ค์ธก ๋ฐœํ–‰ SQL (๊ฒฉ๋ฆฌ ๊ด€์ฐฐ, 02 ๋ฌธ์„œ์— ์ „๋ฌธ)
- DDL: id bigint generated by default as identity(AUTO_INCREMENT ์•„๋‹˜),
  start_at time(6) not null unique(์ธ๋ผ์ธยท๋ฌด๋ช…), varchar(30)/varchar(255).
- INSERT: insert into theme (description, name, thumbnail_url, id) values (?,?,?,default)
  ยท id ๊ฐ€ ์ปฌ๋Ÿผ์— ํฌํ•จ๋˜์–ด default ๋กœ ๋ฐœํ–‰(Hibernate 6, 5์™€ ๋‹ค๋ฆ„)
  ยท ์ปฌ๋Ÿผ ์ˆœ์„œ = ์•ŒํŒŒ๋ฒณ์ˆœ(id ๋งˆ์ง€๋ง‰), ์„ ์–ธ/์†๊ธ€์”จ ์ˆœ์„œ ์•„๋‹˜
  ยท IDENTITY ๋ผ save ์‹œ์  ์ฆ‰์‹œ INSERT, ๋ฐ”์ธ๋”ฉ (n:TYPE) <- [๊ฐ’] ๋ถ„๋ฆฌ ๋กœ๊น…

โ–  ํ…Œ์ŠคํŠธ
- ์‚ญ์ œ ์˜ˆ์ •(ํ›„์† ์ปค๋ฐ‹): JdbcThemeRepositoryTestยทJdbcReservationTimeRepositoryTest
  (๋Œ€์ƒ ํด๋ž˜์Šค ์ œ๊ฑฐ). JPA ์–ด๋Œ‘ํ„ฐ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ๋Š” reservation ์—”ํ‹ฐํ‹ฐํ™”(1-2) ์ดํ›„๋กœ ์ด์—ฐ.
- ThemeServiceTestยทReservationTimeServiceTest ๋Š” ํฌํŠธ ์˜์กด์ด๋ผ ๋ฌด์˜ํ–ฅ.
- ์ „์ฒด 144๊ฐœ ํ†ต๊ณผ(./gradlew test BUILD SUCCESSFUL).

โ–  ๋ฌธ์„œ
- docs/02-step1-jpa-mapping.md 1-1 ๋Œ€ํญ ํ™•์žฅ: ์ž๊ธฐ์ง„๋‹จ ๊ฐ€์„ค, ์ „ํ™˜ ์ „/ํ›„ ์ฝ”๋“œ,
  ๋ฐœํ–‰ SQL ์‹ค์ธก ์ „๋ฌธ, ๊ด€์ฐฐโ‘ โ‘กโ‘ข(์˜ˆ์ธก/์‹ค์ œ), ํ™•์ธ ๊ณผ์ œ ๋‹ต, ๊ฐญ ๋ถ„์„,
  ๊ฒฐ์ • ๊ธฐ๋ก(A/B/C), ํ…Œ์ŠคํŠธ ๋ณ€๊ฒฝ, ํ”ผ๋“œ๋ฐฑ ์‹ ํ˜ธ, ๋ง‰ํž˜โ†’๋‹ค์Œ ์ •์˜, ์ธ์‚ฌ์ดํŠธ.
์ง์ „ ๋งคํ•‘ ์ „ํ™˜ ์ปค๋ฐ‹์˜ ํ•™์Šต ๊ธฐ๋ก. 1-1 ์„น์…˜์„ LMS ์ œ์ถœ ์ˆ˜์ค€์œผ๋กœ ํ™•์žฅํ•œ๋‹ค.

- ๋“ค์–ด๊ฐ€๊ธฐ ์ „ ์ž๊ธฐ์ง„๋‹จ: Repository SQL ํŒจํ„ด ๊ฐ€์„ค, ๊ฐ์ฒด ์ฐธ์กฐ ์ž์—ฐ์Šค๋Ÿฌ์›€ ๊ฐ€์„ค
- ์ „ํ™˜ ์ „(JDBC: INSERT+KeyHolder+RowMapper) / ์ „ํ™˜ ํ›„(JPA @entity + ํฌํŠธ ์ˆœ์ˆ˜+์–ด๋Œ‘ํ„ฐ) ์ฝ”๋“œ์™€ "์™œ"
- ๋ฐœํ–‰ SQL ์‹ค์ธก ์ „๋ฌธ: Hibernate ์ƒ์„ฑ DDL(generated by default as identity, time(6),
  ๋ฌด๋ช… ์ธ๋ผ์ธ unique, varchar ๊ธธ์ด) + save INSERT(id default ํฌํ•จยท์•ŒํŒŒ๋ฒณ ์ปฌ๋Ÿผ์ˆœยท๋ฐ”์ธ๋”ฉ ๋ถ„๋ฆฌ ๋กœ๊น…)
- ๊ด€์ฐฐ โ‘ โ‘กโ‘ข ํ‘œ(์˜ˆ์ธก vs ์‹ค์ œ), ํ™•์ธ ๊ณผ์ œ ๋‹ต(์˜ˆ์•ฝ INSERT๊ฐ€ JDBC์™€ ๊ฐ™๊ณ  ๋‹ค๋ฅธ 3๊ฐ€์ง€)
- ๊ฐญ ๋ถ„์„(์ ์ค‘/๋น—๋‚˜๊ฐ„ 3์‹ ํ˜ธ), ๊ฒฐ์ • ๊ธฐ๋ก 4๊ฑด(์ง์ ‘ @entity / ํฌํŠธ ์ˆœ์ˆ˜+์–ด๋Œ‘ํ„ฐ / nativeยทJdbcTemplate / ddl-auto=none) โ€” A/B/C ํƒœ๊ทธ
- ํ…Œ์ŠคํŠธ ๋ณ€๊ฒฝ(์Šฌ๋ผ์ด์Šค ์‚ญ์ œยท144 greenยทschema ์ถฉ๋Œ ์ˆ˜์ •), ํ”ผ๋“œ๋ฐฑ ์‹ ํ˜ธ, ๋ง‰ํž˜โ†’๋‹ค์Œ ์ •์˜, ์ธ์‚ฌ์ดํŠธ

1-2ยท1-3 ์ดํ•˜ ํ…œํ”Œ๋ฆฟ์€ ๋ณด์กด(๋ฏธ์ž‘์„ฑ ์œ ์ง€).
CRUD๋Š” ThemeJpaRepository(Spring Data)์— ์œ„์ž„, findPopularBetween์€ ์–ด๋Œ‘ํ„ฐ์˜
๊ณผ๋„๊ธฐ JdbcTemplate๋กœ ์ด์ „ ์™„๋ฃŒ. ํฌํŠธ(ThemeRepository) ๊ตฌํ˜„์ด ์–ด๋Œ‘ํ„ฐ๋กœ ์ผ์›ํ™”๋จ.
โ€ฆdapter(JPA)๋กœ ๋Œ€์ฒด

CRUD๋Š” ReservationTimeJpaRepository(Spring Data)์— ์œ„์ž„, findAvailable์€ native @query๋กœ
์ด์ „ ์™„๋ฃŒ. ํฌํŠธ(ReservationTimeRepository) ๊ตฌํ˜„์ด ์–ด๋Œ‘ํ„ฐ๋กœ ์ผ์›ํ™”๋จ.
JPA ์–ด๋Œ‘ํ„ฐ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ(@DataJpaTest)๋Š” reservation ์—”ํ‹ฐํ‹ฐํ™”๋กœ findPopular๋ฅผ
์Šฌ๋ผ์ด์Šค์—์„œ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•œ 1-2 ์ดํ›„๋กœ ์ด์—ฐ. ThemeServiceTest๋Š” ํฌํŠธ ์˜์กด์ด๋ผ ์œ ์ง€.
softmoca added 14 commits June 18, 2026 13:04
โ€ฆmeRepository) ์‚ญ์ œ๋กœ ๋ฌดํšจํ™”

JPA ์–ด๋Œ‘ํ„ฐ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ(@DataJpaTest)๋Š” reservation ์—”ํ‹ฐํ‹ฐํ™”๋กœ findAvailable์„
์Šฌ๋ผ์ด์Šค์—์„œ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•œ 1-2 ์ดํ›„๋กœ ์ด์—ฐ. ReservationTimeServiceTest๋Š” ํฌํŠธ ์˜์กด์ด๋ผ ์œ ์ง€.
๋…๋ฆฝ ํด๋ž˜์Šค(1-1)์— ์ด์–ด ์—ฐ๊ด€๊ด€๊ณ„ ์žˆ๋Š” ReservationยทWaiting์„ @entity๋กœ ์ „ํ™˜ํ•œ๋‹ค.
์ด ๋‘˜์ด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋˜๋ฉด์„œ 1-1์˜ ๊ณผ๋„๊ธฐ ๊ตฐ๋”๋”๊ธฐ(native/JdbcTemplate/ddl-auto=none)๊ฐ€
ํ›„์† ์ปค๋ฐ‹์—์„œ ์ œ๊ฑฐ๋œ๋‹ค.

โ–  ์—”ํ‹ฐํ‹ฐ (๋‹จ๋ฐฉํ–ฅ @manytoone)
- ReservationยทWaiting: @entity, final ์ œ๊ฑฐ + protected ์ƒ์„ฑ์ž, ํŒฉํ† ๋ฆฌยท๊ฒ€์ฆยทpromoteยท
  withOrderIndexยทdateTime ๋ณด์กด.
- @manytoone(optional=false) @joincolumn(time_id/theme_id) โ€” ๋‹จ๋ฐฉํ–ฅ. fetch ๊ธฐ๋ณธ๊ฐ’(EAGER)
  ์œ ์ง€: 1-3 ๊ด€์ฐฐโ‘คยท3-1 N+1์—์„œ ๋ณธ ๋’ค ํŠœ๋‹ํ•˜๋ ค๊ณ  ์ผ๋ถ€๋Ÿฌ ๋ช…์‹œ ์•ˆ ํ•จ.
- @table(uniqueConstraints=...)๋กœ ๋ณตํ•ฉ UNIQUE ๋ณด์กด:
  reservation UNIQUE(date,time_id,theme_id) / waiting 2๊ฐœ(+order_index, +name).
  โ†’ schema.sql ์—†์ด Hibernate DDL์ด ๋ณตํ•ฉ UNIQUE๋ฅผ ์ƒ์„ฑํ•จ์„ ์‹ค์ธก ํ™•์ธ.
- ๋ฐœํ–‰ DDL ๊ด€์ฐฐ: @manytoone @joincolumn โ†’ `time_id bigint not null` +
  `foreign key (time_id) references reservation_time` (๊ฐ์ฒด ์ฐธ์กฐ๊ฐ€ FK ์ปฌ๋Ÿผ+์ œ์•ฝ์œผ๋กœ ์ปดํŒŒ์ผ).

โ–  ๋ฆฌํฌ์ง€ํ† ๋ฆฌ (Spring Data ํŒŒ์ƒ + @Modifying)
- ReservationJpaRepository/ReservationRepositoryAdapter: CRUD ๋‚ด์žฅ +
  existsByDateAndTime_IdAndTheme_Id(_AndName/_AndIdNot)ยทexistsByTime_IdยทexistsByTheme_Idยท
  findByNameOrderByDateAscTime_StartAtAsc(ํŒŒ์ƒ) / updateDateAndTime์€ @Modifying JPQL.
- WaitingJpaRepository/WaitingRepositoryAdapter: findByDateAndTime_IdAndTheme_Id
  OrderByOrderIndexAscยทfindByNameOrderBy...(ํŒŒ์ƒ) / updateOrderIndex์€ @Modifying.

โ–  flush ์ˆœ์„œ ์ˆ˜์ • (4๋‹จ๊ณ„ ์˜ˆ๊ณ  ํ˜„์ƒ์˜ ์‹ค๋ฌผ)
- ์Šน๊ธ‰ ํ๋ฆ„(์ทจ์†Œโ†’๊ฐ™์€ ์Šฌ๋กฏ ์ฒซ ๋Œ€๊ธฐ ์Šน๊ฒฉ): deleteById(em.remove)๋Š” ์ง€์—ฐ๋˜๋Š”๋ฐ
  IDENTITY save๊ฐ€ ์ฆ‰์‹œ INSERT โ†’ ์˜› ์˜ˆ์•ฝ ๋ฏธ์‚ญ์ œ ์ƒํƒœ๋กœ ๊ฐ™์€ ์Šฌ๋กฏ INSERT โ†’
  UNIQUE(date,time_id,theme_id) ์ถฉ๋Œ(ConstraintViolation). JPA๋Š” flush ์‹œ
  INSERTโ†’UPDATEโ†’DELETE๋กœ ์žฌ์ •๋ ฌํ•˜๊ธฐ ๋•Œ๋ฌธ.
- ํ•ด๊ฒฐ: ReservationRepositoryAdapter.deleteById์—์„œ deleteById ํ›„ flush()๋กœ
  delete-before-insert(JDBC์˜ ์ฆ‰์‹œ DELETE ์˜๋ฏธ) ๋ณต์›. ์ „์ฒด ํ…Œ์ŠคํŠธ green.

(ThemeยทReservationTime ๋ฆฌํฌ์ง€ํ† ๋ฆฌ JPQL ์Šน๊ฒฉ + schema.sql ์ œ๊ฑฐ + create-drop ์ „ํ™˜์€ ๋‹ค์Œ ์ปค๋ฐ‹)
Reservation ์ด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋˜์–ด ๊ต์ฐจ ํ…Œ์ด๋ธ” ์ฟผ๋ฆฌ๋ฅผ JPQL๋กœ ์˜ฌ๋ฆฌ๊ณ , DDL ์†Œ์œ ๊ถŒ์„
Hibernate๋กœ ์ผ์›ํ™”ํ•œ๋‹ค. 1-1์—์„œ ๋‚จ๊ฒผ๋˜ ๊ณผ๋„๊ธฐ ์žฅ์น˜๊ฐ€ ์ „๋ถ€ ์‚ฌ๋ผ์ง„๋‹ค.

- ThemeJpaRepository.findPopularBetween: ๊ณผ๋„๊ธฐ JdbcTemplate โ†’ JPQL ์ƒ์„ฑ์ž ํ‘œํ˜„์‹
  (new PopularThemeProjection(t, count(r)) / ์„ธํƒ€์กฐ์ธ / limit์€ Pageable).
  ThemeRepositoryAdapter์—์„œ JdbcTemplate ์˜์กด ์ œ๊ฑฐ.
- ReservationTimeJpaRepository.findAvailable: native @query โ†’ JPQL ์•ˆํ‹ฐ์กฐ์ธ
  (not in (select r.time.id ...)).
- application.properties(mainยทtest): ddl-auto=none โ†’ create-drop +
  defer-datasource-initialization=true (Hibernate DDL ํ›„ data.sql ์‹œ๋“œ).
- schema.sql ์ œ๊ฑฐ + ๊ตฌ Jdbc ๊ตฌํ˜„/์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ์ œ๊ฑฐ๋Š” ๋‹ค์Œ ์ปค๋ฐ‹.

์ „์ฒด ํ…Œ์ŠคํŠธ green(./gradlew test BUILD SUCCESSFUL) ํ™•์ธ ํ›„ ํ‘ธ์‹œ.
๋ณตํ•ฉ UNIQUE ๋Š” @table(uniqueConstraints) ๋กœ ์—”ํ‹ฐํ‹ฐ์— ์ด์ „ ์™„๋ฃŒ. FKยท์‹๋ณ„์ž๋„ ๋งคํ•‘์œผ๋กœ ํ‘œํ˜„.
JPA ์–ด๋Œ‘ํ„ฐ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ(@DataJpaTest)๋Š” ํ›„์† ๋„์ž… ํ›„๋ณด. ์„œ๋น„์Šคยท์ธ์ˆ˜ ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์ž‘ ๋ณด์žฅ ์œ ์ง€.
์ด๋กœ์จ adapter/persistence ์Šฌ๋ผ์ด์Šค์˜ ๊ตฌ Jdbc ํ…Œ์ŠคํŠธ 4์ข… ์ •๋ฆฌ ์™„๋ฃŒ. 1-2 ์ „ํ™˜ ๋.
1-1๊ณผ ๋™์ผํ•œ ๋ฐ€๋„๋กœ 1-2๋ฅผ ๊ธฐ๋ก. 1-2/1-3 ํ…œํ”Œ๋ฆฟ ์ค‘ 1-2๋ฅผ ์ƒ์„ธ๋ณธ์œผ๋กœ ๊ต์ฒด(1-1ยท1-3 ๋ณด์กด).

- ์ง„์ž… ๊ฐ€์„ค 2๊ฐœ(@manytooneโ†’๋ฌด์—‡์ด ์ƒ๊ธฐ๋‚˜ / ๊ต์ฐจ์ฟผ๋ฆฌ JPQLํ™”)
- ์ „ํ™˜ ์ „(3์ค‘ ์กฐ์ธ RowMapper+KeyHolder) / ์ „ํ™˜ ํ›„(@manytoone ๋‹จ๋ฐฉํ–ฅ + ํŒŒ์ƒ์ฟผ๋ฆฌ + @Modifying + JPQL ์Šน๊ฒฉ)์™€ "์™œ"
- ๋ฐœํ–‰ DDL ์‹ค์ธก: @manytoone @joincolumn โ†’ time_id FK ์ปฌ๋Ÿผ + foreign key ์ œ์•ฝ / @table ๋ณตํ•ฉ UNIQUE 3๊ฐœ ๋ณด์กด
- ๋ฐœํ–‰ SQL ์‹ค์ธก(๋Ÿฐํƒ€์ž„): save INSERT(๊ฐ์ฒดโ†’FK ๋ฐ”์ธ๋”ฉ) / findById EAGER ๋‹จ์ผ join fetch / findByName ํŒŒ์ƒ์ฟผ๋ฆฌ์˜ N+1(๋ฉ”์ธ+์—ฐ๊ด€๋ณ„ ๋ณด์กฐ SELECT) / findAvailable ์•ˆํ‹ฐ์กฐ์ธ / findPopular ์„ธํƒ€์กฐ์ธ+group by+fetch first / show-sqlยทSQL ๋กœ๊ฑฐ ์ด์ค‘ ๋กœ๊น…
- ํ™•์ธ ๊ณผ์ œ ๋‹ต: findById().getTime().getStartAt() โ†’ EAGER join fetch๋ผ ์ถ”๊ฐ€ SQL 0
- N+1 ์˜ˆ๊ณ (โ†’3-1): ๊ฐ™์€ EAGER์ธ๋ฐ by-id๋Š” join fetch, ์ฟผ๋ฆฌ๋Š” ๋ณด์กฐ SELECT
- flush ์ˆœ์„œ ๋ฐœ๊ฒฌ(โ†’4๋‹จ๊ณ„): deleteById ์ง€์—ฐ vs IDENTITY save ์ฆ‰์‹œ INSERT โ†’ UNIQUE ์ถฉ๋Œ โ†’ flush()๋กœ delete-before-insert ๋ณต์›
- ๊ฒฐ์ •๊ธฐ๋ก(B): ๋‹จ๋ฐฉํ–ฅ+์–‘๋ฐฉํ–ฅ/cascade ๋ช…์‹œ์  ๋น„๊ฒฐ์ • / fetch ๊ธฐ๋ณธ๊ฐ’ ์œ ์ง€ ์•„ํฌ / @Modifying vs dirty checking / updateDateAndTime time ์ฐธ์กฐ
- ํ…Œ์ŠคํŠธ(C)ยทํ”ผ๋“œ๋ฐฑ์‹ ํ˜ธ(C)ยท๋ง‰ํž˜โ†’๋‹ค์Œ์ •์˜ยท์ธ์‚ฌ์ดํŠธยทํšŒ์ˆ˜ ์ฐจ์›
1-3 ํ…œํ”Œ๋ฆฟ์„ ์‹ค์ธก ์ƒ์„ธ๋ณธ์œผ๋กœ ๊ต์ฒดํ•˜๊ณ , ๋์˜ doc-๋ ˆ๋ฒจ ํ…œํ”Œ๋ฆฟ์„ "1๋‹จ๊ณ„ ์ข…ํ•ฉ(A/B/C ์ด๊ด„)"๋กœ ๋Œ€์ฒด.

6๊ฐœ ๊ด€์ฐฐ(4ํ•„๋“œ: ์‹œ๋„/์˜ˆ์ธก/์‹ค์ œ/์™œ + ๋ฐœํ–‰ SQL):
- โ‘  dirty checking: ์„ธํ„ฐ ์—†๋Š” ๋„๋ฉ”์ธ์ด๋ผ ๋ฆฌํ”Œ๋ ‰์…˜ ๊ฐ•์ œ โ†’ commit ์‹œ full-column UPDATE ๋ฐœํ–‰. 1-2 ๋ฏธ๊ฒฐ(@Modifying vs dirty checking)์„ @Modifying ์œ ์ง€๋กœ ์ข…๊ฒฐ
- โ‘ก 1์ฐจ ์บ์‹œ: ๊ฐ™์€ tx 2๋ฒˆ์งธ find SELECT 0, t1==t2 ๋™์ผ ์ธ์Šคํ„ด์Šค
- โ‘ข ์“ฐ๊ธฐ ์ง€์—ฐ: IDENTITY persist ์ฆ‰์‹œ INSERT โ†’ 1-1 โ‘ข ๋ณต์„  ํšŒ์ˆ˜ + 1-2 flush ๋ฒ„๊ทธ ๋ฟŒ๋ฆฌ ํ™•์ฆ
- โ‘ฃ flush ์‹œ์ : dirty ์ƒํƒœ JPQL ์ง์ „ auto-flush(UPDATEโ†’count)
- โ‘ค fetch ๊ธฐ๋ณธ๊ฐ’: @manytoone EAGER ๋‹จ์ผ ์กฐ์ธ. @onetomany ๋ถ€์žฌ(๋‹จ๋ฐฉํ–ฅ ๊ฒฐ์ •์˜ ๊ฒฐ๊ณผ) ๊ธฐ๋ก
- โ‘ฅ LazyInit: EAGER๋Š” detach ํ›„ ์ •์ƒ / getReference ํ”„๋ก์‹œ๋Š” tx ๋ฐ– LazyInitializationException โ†’ reads tx-free + 3-1 LAZY ์ „ํ™˜ ์œ„ํ—˜ ์ขŒํ‘œ

+ ๊ฒฐ์ •(B): @Modifying ์œ ์ง€ ํ™•์ • / reads tx-free LAZY ์ „ํ™˜ ์‹œ ์žฌ๊ฒ€ํ† 
+ 1๋‹จ๊ณ„ ์ข…ํ•ฉ: A/B/C ์ด๊ด„ + ๋‹ค์Œ ๋‹จ๊ณ„ ๋ณต์„ (3-1 N+1, 4๋‹จ๊ณ„ flush, 2๋‹จ๊ณ„ member, LAZY read)
๊ธฐ๋Šฅ์€ 1-2 findByName ํŒŒ์ƒ์ฟผ๋ฆฌ๋กœ ์ด๋ฏธ ์ถฉ์กฑ โ†’ 2๋‹จ๊ณ„๋Š” ๊ฒฐ์ • ๋ช…์‹œ + mine N+1 ๊ด€์ฐฐ + ๊ธฐ๋ก.

- ์ž๊ธฐ์ง„๋‹จ: ๋‹จ์ผ ์กฐ๊ฑด+์ •๋ ฌ์ด๋ผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ ์ง๊ฐ
- ํ˜„ํ™ฉ: GET /reservations-mine ํ๋ฆ„(์˜ˆ์•ฝยท๋Œ€๊ธฐ ๊ฐ findByName + in-memory ๋ณ‘ํ•ฉ ์ •๋ ฌ)
- ๋ฐœํ–‰ SQL ๊ด€์ฐฐ(์˜ˆ์•ฝ2+๋Œ€๊ธฐ2): distinct 8 SELECT
  ยท N+1 ์‹ค๋ฌผ(์˜ˆ์•ฝยท๋Œ€๊ธฐ ๋‘ ๊ฐˆ๋ž˜)
  ยท 1์ฐจ ์บ์‹œ๊ฐ€ ๊ณต์œ  theme dedup(naive 1+2N ์•„๋‹˜)
  ยท reads tx-free์˜ ์ฒญ๊ตฌ์„œ: ์˜ˆ์•ฝ tx์™€ ๋Œ€๊ธฐ tx๊ฐ€ ์บ์‹œ ๋น„๊ณต์œ  โ†’ theme ์žฌ๋กœ๋“œ(tx ๊ฒฝ๊ณ„=์บ์‹œ ๋ฒ”์œ„)
- ํ™•์ธ ๊ณผ์ œ: ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ ์‚ฌ์šฉ / ํ•œ๊ณ„(์ด๋ฆ„ ํญ๋ฐœยทfetch join ๋ถˆ๊ฐ€ยท์—”ํ‹ฐํ‹ฐ ๊ฒฐํ•ฉ ๋ถˆ๊ฐ€)
- ๊ฒฐ์ •1: name ์œ ์ง€(0๋‹จ๊ณ„ ์žฌ๊ตฌํ˜„ ๊ธˆ์ง€ยท1:1 ๋น„๊ต ๋ณด์กด vs ๋™๋ช…์ด์ธ ๋ถˆ๊ฐ€)
- ๊ฒฐ์ •2: ๋ฉ”์„œ๋“œ ์ฟผ๋ฆฌ(์ตœ์†Œ ์ฝ”๋“œ vs fetch join ๋ถˆ๊ฐ€ โ†’ 3-1 ์Šน๊ฒฉ)
- ํ…Œ์ŠคํŠธ: ์ฝ”๋“œ ๋ณ€๊ฒฝ 0ยท134 green / ๋ง‰ํž˜โ†’3-1 ์ž…๊ตฌ / ์ธ์‚ฌ์ดํŠธยทํšŒ์ˆ˜ ์ฐจ์›
1-2/1-3์—์„œ ์˜๋„์ ์œผ๋กœ ์•ˆ๊ณ  ์˜จ N+1์„ ๋‹ซ๋Š”๋‹ค. "๊ธฐ๋ณธ๊ฐ’(EAGER)โ†’๋ฌธ์ œ(N+1/LazyInit)โ†’ํ•ด๊ฒฐ(fetch join)" ์•„ํฌ์˜ ์ข…์ฐฉ.

โ–  ๋ฌธ์ œ ์žฌํ˜„ (๊ด€์ฐฐ A)
- EAGER + ํŒŒ์ƒ ์ฟผ๋ฆฌ๋ผ mine ๊ฒฝ๋กœ(์˜ˆ์•ฝ N + ๋Œ€๊ธฐ M)๊ฐ€ main + ์—ฐ๊ด€๋ณ„ ๋ณด์กฐ SELECT.
  ์˜ˆ์•ฝ2+๋Œ€๊ธฐ2 ๊ธฐ์ค€ distinct 8 SELECT(์˜ˆ์•ฝ main+theme+timeร—2, ๋Œ€๊ธฐ main+theme+timeร—2).
- 1์ฐจ ์บ์‹œ๊ฐ€ ๊ฐ™์€ tx์˜ ๊ณต์œ  theme๋Š” dedupํ•˜์ง€๋งŒ, reads tx-free๋ผ ์˜ˆ์•ฝ tx์™€ ๋Œ€๊ธฐ tx๊ฐ€
  ์บ์‹œ ๋น„๊ณต์œ  โ†’ theme ์žฌ๋กœ๋“œ(tx ๊ฒฝ๊ณ„=์บ์‹œ ๋ฒ”์œ„). (2๋‹จ๊ณ„์—์„œ ๊ธฐ๋ก)

โ–  EAGERโ†’LAZY ์ „ํ™˜์˜ ์ฒญ๊ตฌ์„œ (1-3 โ‘ฅ ์˜ˆ๊ณ  ์‹คํ˜„)
- ReservationยทWaiting์˜ @manytoone(time/theme)๋ฅผ fetch=LAZY๋กœ.
- ๊ทธ ์ˆœ๊ฐ„ findMyReservationsAndWaitings(@transactional ์•„๋‹˜, reads tx-free)์˜ DTO ๋ณ€ํ™˜์—์„œ
  LazyInitializationException 3๊ฑด(ReservationServiceTest) โ†’ 1-3 โ‘ฅ(getReference ํ”„๋ก์‹œ tx๋ฐ– ์ ‘๊ทผ)
  ์˜ˆ๊ณ ๊ฐ€ ์‹ค์ œ read ๊ฒฝ๋กœ์—์„œ ๊ทธ๋Œ€๋กœ ์žฌํ˜„.
- ๊ด€์ฐฐ: findAll(admin)์€ ์•ˆ ๊นจ์ง โ€” OSIV(open-in-view ๊ธฐ๋ณธ on)๊ฐ€ HTTP ๊ฒฝ๋กœ์˜ LAZY๋ฅผ ๋ทฐ๊นŒ์ง€
  ์—ด์–ด ๊ฐ€๋ ค์คŒ. ๋‹จ OSIV๋Š” LazyInit๋งŒ ๊ฐ€๋ฆฌ๊ณ  N+1์€ ๋ชป ๊ฐ€๋ฆผ(์„œ๋น„์Šค ์ง์ ‘ ํ˜ธ์ถœ ํ…Œ์ŠคํŠธ๋Š” ๋…ธ์ถœ).

โ–  ํ•ด๊ฒฐ (๊ด€์ฐฐ B)
- ์กฐํšŒ ๊ฒฝ๋กœ์— @EntityGraph(attributePaths={"time","theme"}):
  ยท ReservationJpaRepository.findByName...(mine) + findAll(admin) ์˜ค๋ฒ„๋ผ์ด๋“œ
  ยท WaitingJpaRepository.findByName...(mine)
- mine ๊ฒฝ๋กœ 8 โ†’ 2 SELECT(์˜ˆ์•ฝ fetch-join 1 + ๋Œ€๊ธฐ fetch-join 1). N+1 ์ œ๊ฑฐ + LazyInit ํ•ด์†Œ.
- @manytoone(to-one) fetch join์ด๋ผ row ์ค‘๋ณต ์—†์Œ(distinct ๋ถˆํ•„์š”) โ€” ์ค‘๋ณต์€ to-many ์ปฌ๋ ‰์…˜
  fetch join์˜ ๋ฌธ์ œ. optional=false๋ผ INNER JOIN์œผ๋กœ ๋ฐœํ–‰.

์ „์ฒด ํ…Œ์ŠคํŠธ green(./gradlew test BUILD SUCCESSFUL) ํ™•์ธ ํ›„ ํ‘ธ์‹œ.
3-1ยท3-2 ํ…œํ”Œ๋ฆฟ์„ ์‹ค์ธก ์ƒ์„ธ๋ณธ์œผ๋กœ ์ฑ„์›€.

3-1 (N+1):
- ์ž๊ธฐ์ง„๋‹จ: Waiting ๋ถ„๋ฆฌ ์—”ํ‹ฐํ‹ฐ ๊ทผ๊ฑฐ(์Šฌ๋กฏ๋‹น ์˜ˆ์•ฝ/๋Œ€๊ธฐ ์œ ์ผ์„ฑ ๊ทœ์น™ ๋ถ„๋ฆฌ)
- ๊ด€์ฐฐ A: EAGER mine 8 SELECT(1์ฐจ์บ์‹œ dedupยทtx ๊ฒฝ๊ณ„=์บ์‹œ ๋ฒ”์œ„)
- ์ค‘๊ฐ„: EAGERโ†’LAZY โ†’ ReservationServiceTest 3๊ฑด LazyInit(1-3 โ‘ฅ ์‹คํ˜„) / findAll์€ OSIV๊ฐ€ ๊ฐ€๋ฆผ(LazyInit๋งŒ, N+1์€ ๋ชป ๊ฐ€๋ฆผ)
- ๊ด€์ฐฐ B: @EntityGraph fetch join โ†’ 8โ†’2, ๋‹จ์ผ INNER JOIN, to-one์ด๋ผ row ์ค‘๋ณต ์—†์Œ(distinct ๋ถˆํ•„์š”)
- ๋‘ SQL ๋‚˜๋ž€ํžˆ ํ‘œ

3-2 (JPQL rank):
- A(order_index=3) vs B(JPQL ์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ COUNT=2) ๊ด€์ฐฐ + ๋ฐœํ–‰ SQL
- ํ™•์ธ๊ณผ์ œ: JPQL SQL / ํŒŒ์ƒ์ฟผ๋ฆฌ๋กœ ๋ชป ํ‘ธ๋Š” ์ด์œ (์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ+์ž๊ธฐ์ฐธ์กฐ ์ง‘๊ณ„๋Š” ๋ฌธ๋ฒ• ๋ฐ–)
- ๊ฒฐ์ •: order_index ์œ ์ง€(UNIQUE ๋ฐฉ์–ด์„ ยท์Šน๊ธ‰ ํ†ตํ•ฉ vs JPQL rank์˜ ๋ฌด๋ถ€๊ธฐยท์ž๊ฐ€์น˜์œ ) โ€” ๋น„๊ต ํ›„ ์œ ์ง€, JPQL rank๋Š” ๋ฏธ์…˜ ํ›„ ๋ฆฌํŒฉํ„ฐ๋ง 1์ˆœ์œ„๋กœ ๊ธฐ๋ก(์‹œ๋„โ†’ํ›„ํ‡ด)

+ ํ…Œ์ŠคํŠธ(C): LazyInitโ†’@EntityGraphยทgreen(134) / ๋ง‰ํž˜โ†’4๋‹จ๊ณ„ + ๋น„๊ด€์  ๋ฝ ๋ฐฑ๋กœ๊ทธ / ์ธ์‚ฌ์ดํŠธยทํšŒ์ˆ˜ ์ฐจ์›
์ž๋™ ์Šน์ธ์€ 1-2์—์„œ ๊ตฌํ˜„ยทflush ํ”ฝ์Šค ์™„๋ฃŒ โ†’ 4๋‹จ๊ณ„๋Š” flush ์ˆœ์„œ ์ •์‹ ๊ด€์ฐฐ + ๊ฒฝ๊ณ„ ๊ฒฐ์ • + ๊ธฐ๋ก. ์ฝ”๋“œ ๋ณ€๊ฒฝ 0.

- ์ž๊ธฐ์ง„๋‹จ: ์ž๋™์Šน์ธ ์œ„์น˜=Service @transactional / ๊ฒฝ๊ณ„=์ทจ์†Œ+์Šน๊ธ‰ ํ•œ ํŠธ๋žœ์žญ์…˜
- ๋ณธ์งˆ์‹ ํ˜ธ โ‘  ๊ฒฝ๊ณ„: ํ•œ @transactional ์›์ž์„ฑ(๋ถ„๋ฆฌ ์‹œ ๋นˆ ์Šฌ๋กฏ) โ€” ๋‹จ ๊ฒฝ๊ณ„โ‰ flush ์ˆœ์„œ
- โ‘ก ๋™์‹œ์„ฑ: ์˜๋„์  deferred(Level 3 ๋ฝ ์•„ํฌ), UNIQUE๊ฐ€ ์ตœ์•…๋งŒ ๋ฐฉ์–ด
- โ‘ข flush ์ˆœ์„œ(์‹ค์ธก): deleteByOwner 1ํšŒ โ†’
  SELECT R โ†’ SELECT time(LAZY) โ†’ DELETE reservation(โ‘ , flush) โ†’ SELECT waiting โ†’
  INSERT reservation(โ‘ก) โ†’ DELETE waiting(โ‘ข) โ†’ UPDATE waiting(โ‘ฃ)
  ยท ํ•ต์‹ฌ: ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„(์›์ž์„ฑ) โ‰  flush ์ˆœ์„œ(์•ก์…˜ํ INSERTโ†’UPDATEโ†’DELETE) โ†’ delete-before-insert๋Š” ๋ช…์‹œ flush๋กœ๋งŒ
  ยท IDENTITY ์ฆ‰์‹œ INSERT(1-3 โ‘ข)๊ฐ€ ์ถฉ๋Œ ์•…ํ™”
- ํ™•์ธ๊ณผ์ œ: ์ž๋™์Šน์ธ ์ฑ„ํƒ / ๊ฐ€์žฅ ์•ฝํ•œ ์ถ•=๋™์‹œ์„ฑ
- ๊ฒฐ์ •: ๋‹จ์ผ @transactional + ๋ช…์‹œ flush, ๋™์‹œ์„ฑ deferred ๋ช…์‹œ
- ํ…Œ์ŠคํŠธ(C): ์ฝ”๋“œ ๋ณ€๊ฒฝ 0ยทgreen(134), ์Šน๊ธ‰ ๋กค๋ฐฑ ํ…Œ์ŠคํŠธ(@MockitoSpyBean ํฌํŠธ spy) ๋ฌด์˜ํ–ฅ
- ๐Ÿ“Œ PR ๋ณธ๋ฌธ ์žฌ๋ฃŒ ์ฑ„์›€(๋„๋‹ฌ์ง€์ /๋ธŒ๋žœ์น˜ยท๋ฒ”์œ„/๋ง์„ค์ธ ๊ฒฐ์ •/SQL ๋ฐœ์ทŒ/ํ”๋“ค๋ฆฐ ์žฅ๋ฉด) โ†’ 07 ์กฐ๋ฆฝ์šฉ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant