DevLog ๐Ÿ˜ถ

[JPA] OSIV ์ฐ๋จนํ•˜๊ธฐ 1ํŽธ - ํ”„๋ก์‹œ ๊ฐ์ฒด์™€ ์ค€์˜์† ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ ๋ณธ๋ฌธ

Back-end/JPA

[JPA] OSIV ์ฐ๋จนํ•˜๊ธฐ 1ํŽธ - ํ”„๋ก์‹œ ๊ฐ์ฒด์™€ ์ค€์˜์† ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ

dolmeng2 2023. 5. 21. 16:44

๊ธˆ์š”์ผ์— ๊ทผ๋กœํ•˜๋ฉด์„œ ๊ตฌ๊ตฌ์™€ ๊ทผ๋กœ ํฌ๋ฃจ๋“ค์˜ ๋„์›€ ๋•๋ถ„์— JPA๋ฅผ ๋‹ค์‹œ๊ธˆ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋๋‹ค ๐Ÿ˜…

๋‚˜๋„ ์™„์ „ํ•˜๊ฒŒ ์ž˜ ์•„๋Š” ๋‚ด์šฉ์€ ์•„๋‹Œ์ง€๋ผ ์•„๋Š”๋Œ€๋กœ ์ •๋ฆฌํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

 


 

๐ŸŒฑ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ํŠธ๋žœ์žญ์…˜

๐Ÿ’ก ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ž€?
์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์†ํ™”์‹œํ‚ค๋Š” ํ™˜๊ฒฝ. EntityManager๋ฅผ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•˜๋ฉด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋ณด๊ด€๋œ ์—”ํ‹ฐํ‹ฐ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋œ๋‹ค. EntityManager ์ƒ์„ฑ ์‹œ 1๊ฐœ๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค.

๐Ÿ’ฌ ํŠน์ง•
- ์‹๋ณ„์ž ๊ฐ’์œผ๋กœ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค.
- ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— flush. (์“ฐ๊ธฐ ์ง€์—ฐ - ๋‚ด๋ถ€ ์ฟผ๋ฆฌ ์ €์žฅ์†Œ์— SQL ์ €์žฅ ํ›„ ํ•œ ๋ฒˆ์— ํ”Œ๋Ÿฌ์‹œ)
- 1์ฐจ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. (์ฒ˜์Œ์— 1์ฐจ ์บ์‹œ์—์„œ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ > ์—†์œผ๋ฉด DB ์กฐํšŒ > 1์ฐจ ์บ์‹œ์— ์ €์žฅํ•˜๋Š” ํ˜•ํƒœ = '์˜์†ํ™”')
- 1์ฐจ ์บ์‹œ์— ์ €์žฅ๋œ ์—”ํ‹ฐํ‹ฐ๋Š” == ๋น„๊ต๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. (๋™์ผ์„ฑ ๋ณด์žฅ)
- ๋ณ€๊ฒฝ ๊ฐ์ง€ ('์˜์† ์ƒํƒœ์˜' ๋ณ€๊ฒฝ๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด์„œ ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ ์ €์žฅ > ํ”Œ๋Ÿฌ์‹œ > ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹)

ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋˜๋ฉด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋„ ์ƒ์„ฑ๋˜๊ณ , ์ข…๋ฃŒ๋˜๋ฉด ์ปจํ…์ŠคํŠธ๋„ ํ•จ๊ป˜ ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ , ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ๋Š” ํ•ญ์ƒ ๊ฐ™์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ ‘๊ทผํ•˜๊ฒŒ ๋œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์€ ์šฐ๋ฆฌ๊ฐ€ ์Šคํ”„๋ง์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์‚ฌ์šฉํ•œ @Transactional ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋ฒ”์œ„๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

@Transactional์ด ๋ถ™์–ด์žˆ์œผ๋ฉด ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ํŠธ๋žœ์žญ์…˜ AOP๊ฐ€ ๋จผ์ € ๋™์ž‘ํ•˜๋Š”๋ฐ (์ด์— ๋Œ€ํ•ด์„œ๋Š” ์ถ”ํ›„ ๋‹ค๋ฃจ์–ด ๋ณด๊ฒ ๋‹ค.) ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒ๊ฐํ•˜๋ฉด ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜๋ฉด ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ํ›„ ์ข…๋ฃŒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด๋•Œ, JPA์—์„œ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ํ”Œ๋Ÿฌ์‹œํ•˜์—ฌ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ DB์— ๋ฐ˜์˜ํ•œ ๋’ค, DB ํŠธ๋žœ์žญ์…˜์„ ์ปค๋ฐ‹ํ•˜๊ฒŒ ๋œ๋‹ค.

ํ”ํžˆ ์šฐ๋ฆฌ๋Š” ๊ด€์Šต์ ์œผ๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ์„œ๋น„์Šค ๋ ˆ์ด์–ด์— ๋ถ™์ด๊ณค ํ•œ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋น„์Šค ๊ณ„์ธต์ด ๋๋‚˜๋Š” ์‹œ์ ์— ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ๋˜๋ฉฐ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋„ ํ•จ๊ป˜ ์ข…๋ฃŒ๋˜์–ด, ์„œ๋น„์Šค์™€ ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ ๋ ˆ์ด์–ด์—์„œ๋Š” ์˜์† ์ƒํƒœ์ธ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ๋ทฐ ๊ฐ™์€ presentation layer์—์„œ๋Š” ์ค€์˜์† ์ƒํƒœ๊ฐ€ ๋œ๋‹ค.

 

๊ทธ๋ž˜์„œ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ๋ทฐ (ํ˜น์€ DTO)์—์„œ ์ค€์˜์† ์ƒํƒœ์ธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ†ตํ•ด ์ง€์—ฐ ๋กœ๋”ฉ์„ ์‹œ๋„ํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

๐Ÿ’ก ์ง€์—ฐ ๋กœ๋”ฉ (Lazy Loading)์ด๋ž€?
- ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด ์‹ค์ œ DB์—์„œ ๋ฐ”๋กœ ์ ‘๊ทผํ•˜์ง€ ์•Š๊ณ , ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์‹œ์ ์— ๋กœ๋”ฉํ•˜๋Š” ๋ฐฉ๋ฒ•. ์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด ๋Œ€์‹ ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๋ฅผ ์ง€์—ฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์งœ ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, ์ด๋ฅผ ์œ„ํ•ด ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
- OneToMany, ManyToMany์—์„œ ๋””ํดํŠธ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ „๋žต์ด๋‹ค.

 

ํ•˜์ง€๋งŒ, ์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœํ•  ๋•Œ๋Š” ๋‹ค๋ฅด๋‹ค.

์ฒ˜์Œ ์Šคํ”„๋ง๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ™œ์„ฑํ™”์‹œํ‚ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ๊ตฌ๊ฐ€ ๋œจ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

๊ธฐ๋ณธ์ ์œผ๋กœ ์Šคํ”„๋ง๋ถ€ํŠธ๋Š” OSIV๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— view rendering ์‹œ์ ๊นŒ์ง€ DB ์ฟผ๋ฆฌ์˜ ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ด๋ก ๋งŒ ๋ณด๋ฉด ๋‹น์—ฐํžˆ ์ดํ•ดํ•˜๊ธฐ ํž˜๋“ค๋‹ค. ๊ฐ„๋‹จํ•œ ํด๋ž˜์Šค๋ฅผ ๊ตฌ์„ฑํ•˜์—ฌ ์ง์ ‘ ์‚ดํŽด๋ณด์ž.

(OSIV์— ๋Œ€ํ•ด์„œ๋Š” ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ๋” ์ž์„ธํžˆ ๋‹ค๋ฃฐ ์˜ˆ์ •์ด๋‹ค.)

 


 

๐ŸŒฑ ํ”„๋ก์‹œ ๊ฐ์ฒด

JPA๋ฅผ ์ƒ๊ฐํ•˜๋ฉด ๋บ„ ์ˆ˜ ์—†๋Š” ๊ฐœ๋… ์ค‘ ํ•˜๋‚˜๊ฐ€ ํ”„๋ก์‹œ ๊ฐ์ฒด์ด๋‹ค.

์ฝ”๋“œ๋ฅผ ๋ณด๊ธฐ ์ „์— ๊ฐ„๋‹จํ•˜๊ฒŒ ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์–ด๋–ค ๊ฒƒ์ธ์ง€ ์งš๊ณ  ๋„˜์–ด๊ฐ€๋ณด์ž.

 

JPA์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•˜๋‚˜ ์กฐํšŒํ•  ๋•Œ๋Š” EntityManager.find()๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—†์œผ๋ฉด ์‹ค์ œ DB๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋Ÿฐ ์‹์œผ๋กœ ์ง์ ‘ ์กฐํšŒํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ์‚ฌ์šฉ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๋ฌด์กฐ๊ฑด DB๋ฅผ ์กฐํšŒํ•˜๊ธฐ ๋•Œ๋ฌธ์— cost๊ฐ€ ๋งค์šฐ ๋†’๋‹ค. ๋งŒ์•ฝ DB ์กฐํšŒ๋ฅผ ์‹ค์ œ ์‚ฌ์šฉ ์‹œ์ ๊นŒ์ง€ ๋ฏธ๋ฃจ๊ณ  ์‹ถ๋‹ค๋ฉด EntityManager.getReference()๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๋Š” DB ์กฐํšŒ ๋Œ€์‹ ์— ์ด๋ฅผ ๋Œ€์ฒดํ•  ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๋Ÿฌํ•œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” '์ƒ์†'์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

์ƒ์„ฑ์ž๊ฐ€ ์•„์˜ˆ ์—†๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์•Œ์•„์„œ ๋งŒ๋“ค์–ด์ฃผ์ง€๋งŒ, ํ•˜๋‚˜๋ผ๋„ ๋‹ค๋ฅธ ์ƒ์„ฑ์ž๊ฐ€ ์žˆ์œผ๋ฉด ๋งŒ๋“ค์–ด์ฃผ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋Š” ์›ฌ๋งŒํ•˜๋ฉด ๋งŒ๋“ค์–ด์ค˜์•ผ ํ•œ๋‹ค.

 

ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‹ค์ œ ๊ฐ์ฒด์— ๋Œ€ํ•œ ์ฐธ์กฐ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€, ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์‹ค์ œ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์ค€๋‹ค.

์œ„์˜ ์ƒํ™ฉ์—์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” crew.getName()์„ ํ†ตํ•ด ์ง์ ‘ ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด ์‹ค์ œ DB๋ฅผ ์กฐํšŒํ•˜์—ฌ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋ฅผ 'ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ ์ดˆ๊ธฐํ™”'๋ผ๊ณ  ํ•œ๋‹ค.

 

1. ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ crew.getName()์„ ํ˜ธ์ถœํ•˜์—ฌ ์‹ค์ œ ๋ฐ์ดํ„ฐ ์กฐํšŒํ•˜๊ธฐ
2. ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—๊ฒŒ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ์˜ ์ƒ์„ฑ ์š”์ฒญ
3. ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” DB๋ฅผ ์กฐํšŒํ•˜์—ฌ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด ์ƒ์„ฑ
4. ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์ƒ์„ฑ๋œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด์˜ ์ฐธ์กฐ๋ฅผ ๋ณด๊ด€
5. ๋ณด๊ด€ํ•œ ์ฐธ์กฐ๊ฐ’(crew)์˜ getName()์„ ํ˜ธ์ถœํ•˜์—ฌ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜

์ด๋Ÿฌํ•œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์ฒ˜์Œ ์‚ฌ์šฉ๋  ๋•Œ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ดˆ๊ธฐํ™”๋œ๋‹ค.

๋˜ํ•œ, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ด๋ฏธ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์žˆ์œผ๋ฉด DB๋ฅผ ์กฐํšŒํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— em.getReference()๋ฅผ ํ˜ธ์ถœํ•ด๋„ ํ”„๋ก์‹œ ๊ฐ์ฒด ๋Œ€์‹  ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค!

 

์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ฐจ์ฐจ JPA ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์˜ฌ๋ฆด ์˜ˆ์ •! ๐Ÿ˜Š


๐ŸŒฑ ํ…Œ์ด๋ธ” ์„ค๊ณ„

โœ”๏ธ Entity

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Wootecho {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private Course course;
}

์šฐํ…Œ์ฝ” ํด๋ž˜์Šค์ด๋‹ค. Course๋ผ๋Š” enum์—๋Š” ๋ฐฑ์—”๋“œ, ํ”„๋ก ํŠธ, ์•ˆ๋“œ๋กœ์ด๋“œ๋ผ๋Š” ๊ณผ์ • ์ •๋ณด๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋‹ค.

 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Crew {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "wootecho_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Wootecho wootecho;
}

๊ฐ„๋‹จํ•œ ํฌ๋ฃจ ํด๋ž˜์Šค์ด๋‹ค.

ํฌ๋ฃจ ํด๋ž˜์Šค์™€ ์šฐํ…Œ์ฝ” ํด๋ž˜์Šค๋Š” N:1์˜ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋•Œ ์ง€์—ฐ ๋กœ๋”ฉ ์ „๋žต์„ ์ฑ„ํƒํ•˜์˜€๋‹ค. (FetchType.LAZY)

๋˜ํ•œ, ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด์— ๋Œ€ํ•œ ์„ค์ •์„ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•˜๋‹ค.

 

์‹ค์ œ ํ…Œ์ด๋ธ”์˜ ๊ด€๊ณ„๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋งบ์–ด์ง„๋‹ค.

 

ํ•˜์ง€๋งŒ, ์ด๋Š” ๋…ผ๋ฆฌ์ ์ธ ์—ฐ๊ด€๊ด€๊ณ„์ผ ๋ฟ์ด๋ฉฐ ์‹ค์ œ๋กœ ํ…Œ์ด๋ธ” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ์กฐ๊ฑด์ด ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

CREATE TABLE `wootecho` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `course` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
CREATE TABLE `crew` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `wootecho_id` bigint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

crew ํ…Œ์ด๋ธ”์€ ๋‹จ์ˆœํžˆ wootecho_id๋ผ๋Š” ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๋ฟ์ด์ง€, ์™ธ๋ž˜ํ‚ค๋กœ ๊ฑธ๋ ค์žˆ์ง€ ์•Š๋‹ค.

 

์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ค์ • ๋•Œ๋ฌธ์ด๋‹ค.

@JoinColumn(... foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Wootecho wootecho;

์—ฌ๊ธฐ์„œ foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT) ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด, ๋ฌผ๋ฆฌ์ ์ธ ์™ธ๋ž˜ํ‚ค๋ฅผ ๋งบ์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

์‹ค์ œ๋กœ ์‹ค๋ฌด์—์„œ๋Š” ์™ธ๋ž˜ํ‚ค๋ฅผ ๊ฑธ์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. 

์™ธ๋ž˜ํ‚ค๋ฅผ ๊ฑธ๋ฉด ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ์€ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์ง€๋งŒ (๋ถ€๋ชจ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์„ ๋•Œ ๊ณ ์•„๋กœ ๋‚จ์€ ์ž์‹ ํ•„๋“œ๊ฐ€ ๋‚จ์ง€ ์•Š๋„๋ก ํ•จ)

์ž์‹ ํ…Œ์ด๋ธ”์˜ INSERT ์—ฐ์‚ฐ ์‹œ์— ๋ถ€๋ชจ ํ…Œ์ด๋ธ”๋„ ํ™•์ธํ•ด์•ผ ํ•˜๊ณ , ๋ณ€๊ฒฝ ์‹œ์—๋„ ๊ณ ๋ คํ•ด์•ผ ํ•  ์ ์ด ๋งŽ๊ณ , ํ…Œ์ŠคํŠธํ•˜๊ธฐ๋„ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์˜ํ•œ๋‹˜์€ ์ด์— ๋Œ€ํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ๋‹ต๋ณ€์„ ๋‚จ๊ฒจ์ฃผ์‹  ์ ์ด ์žˆ๋‹ค.

๋‚˜ ์—ญ์‹œ ๊ฐœ์ธ์ ์œผ๋กœ ์™ธ๋ž˜ํ‚ค๋ฅผ ๊ฑฐ๋Š” ๊ฑธ ์„ ํ˜ธํ•˜์ง€๋งŒ... ์•„๋งˆ ์ž‘์€ ๋„๋ฉ”์ธ๋งŒ ๋งŒ์ ธ๋ดค๊ธฐ ๋•Œ๋ฌธ์ผ ๊ฒƒ ๊ฐ™๋‹ค ๐Ÿ˜…

์•„๋ฌดํŠผ, ์ด๊ฒŒ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ์„ ์œ„์™€ ๊ฐ™์ด ์„ค๊ณ„ํ–ˆ๊ณ , ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ์™€ ์„œ๋น„์Šค, ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

โœ”๏ธ Repository

public interface CrewRepository extends JpaRepository<Crew, Long> {

}

ํ”ํžˆ ์‚ฌ์šฉํ•˜๋Š” Jpa๋ฅผ ์‚ฌ์šฉํ•œ ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ ๊ตฌ์„ฑ์ด๋‹ค.

 

 

โœ”๏ธ Service

@Service
@Transactional(readOnly = true)
@AllArgsConstructor
public class CrewService {

    private final CrewRepository crewRepository;

    public Crew getById(final Long wootechoId) {
        final Crew findCrew = crewRepository.findById(wootechoId)
            .orElseThrow(() -> new IllegalArgumentException("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์•„์ด๋””์ž…๋‹ˆ๋‹ค."));
        return findCrew;
    }
}

์„œ๋น„์Šค์—์„œ๋Š” ๋‹จ์ˆœํžˆ ์•„์ด๋””๋ฅผ ํ†ตํ•ด ํฌ๋ฃจ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•ด์˜ค๋Š” ๋กœ์ง๋งŒ ์žˆ๋‹ค.

 

 

โœ”๏ธ Controller

@RestController
@RequestMapping("/crew")
@AllArgsConstructor
public class CrewController {

    private final CrewService crewService;

    @GetMapping("/{crewId}")
    public ResponseEntity<CrewResponse> getById(@PathVariable Long crewId) {
        final Crew crew = crewService.getById(crewId);
        return ResponseEntity.ok(CrewResponse.of(crew));
    }
}

์ปจํŠธ๋กค๋Ÿฌ์ด๋‹ค. ์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ์€ ์„œ๋น„์Šค์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ›์•„์™€์„œ dto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์„ ์ด๊ณณ์—์„œ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

 

โœ”๏ธ Dto

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class WootechoResponse {
    private final Long id;
    private final String course;

    public static WootechoResponse of(final Wootecho wootecho) {
        return new WootechoResponse(wootecho.getId(), wootecho.getCourse().name());
    }
}
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class CrewResponse {

    private final Long id;
    private final String name;
    private final WootechoResponse wootechoResponse;

    public static CrewResponse of(final Crew crew) {
        return new CrewResponse(crew.getId(), crew.getName(), WootechoResponse.of(crew.getWootecho()));
    }
}

ํฌ๋ฃจ์— ๋Œ€ํ•œ ์‘๋‹ต ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด์„œ crew ์—”ํ‹ฐํ‹ฐ์— ์žˆ๋Š” ๊ฐ’๋“ค๊ณผ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์ธ wootecho ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ๋‹ค.

์ •๋ง ๊ฐ„๋‹จํ•œ ์กฐํšŒ์šฉ API๊ฐ€ ์™„์„ฑ๋˜์—ˆ๋‹ค.

 

ํ˜„์žฌ ๋„๋ฉ”์ธ ๊ตฌ์กฐ๋Š” ์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค.

 

 


๐ŸŒฑ ์ค€์˜์† ์ƒํƒœ์™€ ์ง€์—ฐ ๋กœ๋”ฉ

์ค€์˜์† ์ƒํƒœ์—์„œ๋Š” ์ง€์—ฐ ๋กœ๋”ฉ์ด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ’ก ์ค€์˜์† ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ๋ž€?
์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๋˜ ์˜์† ์ƒํƒœ์ธ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋” ์ด์ƒ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜์ง€ ์•Š๋Š” ์ƒํƒœ

์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์„ค์ •ํ•ด์ฃผ๊ณ , ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ์‹œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•œ๋‹ค.

ํ•˜์ง€๋งŒ, ์ค€์˜์† ์ƒํƒœ์ธ ์—”ํ‹ฐํ‹ฐ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ง€์—ฐ ๋กœ๋”ฉ ์ž์ฒด๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

์ด๋ฅผ ์‹ค์ œ๋กœ ํ™•์ธํ•ด๋ณด์ž.

์šฐ์„ , ์•ž์„œ ๋งํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ์Šคํ”„๋ง๋ถ€ํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ทฐ๊นŒ์ง€ DB ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ€๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์˜ต์…˜์„ ๊บผ์•ผ ํ•œ๋‹ค.

 

spring:
  jpa:
    open-in-view: false

application.yml์—์„œ open-in-view ์˜ต์…˜์„ false๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.

 

๊ทธ๋ฆฌ๊ณ , ํฌ๋ฃจ 1๋ช…์„ ์กฐํšŒํ•˜๋Š” API ์š”์ฒญ์„ ๋‚ ๋ ค๋ณด์ž. ๋‚˜๋Š” '์ ธ๋‹ˆ'๋ผ๋Š” ํฌ๋ฃจ๋ฅผ ์กฐํšŒํ•  ์˜ˆ์ •์ด๋‹ค.

could not initialize proxy [com.example.study.osiv.Wootecho#1] - no Session

์„ธ์…˜์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— proxy๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ๋‚˜์˜จ๋‹ค.

์ง€์—ฐ ๋กœ๋”ฉ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ 'ํ”„๋ก์‹œ ๊ฐ์ฒด'๋กœ์„œ ๋งŒ๋“ค์–ด๋‘๊ณ , ์‚ฌ์šฉ ์‹œ์ ์— ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฐ๋‹ค๊ณ  ๋งํ–ˆ์—ˆ๋‹ค.

// CrewResponse.java
public static CrewResponse of(final Crew crew) {
    return new CrewResponse(crew.getId(), crew.getName(), WootechoResponse.of(crew.getWootecho()));
}

CrewResponse์˜ ์ฝ”๋“œ์ด๋‹ค. ์—ฌ๊ธฐ์„œ crew.getWootecho()๋ฅผ ํ†ตํ•ด ์กฐํšŒ๋ฅผ ํ•  ๋•Œ์—๋Š” ๋‹ด๊ฒจ์žˆ๋˜ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋œ๋‹ค.

์‹ค์ œ๋กœ ๋ณด๋ฉด, HibernateProxy๋ผ๋Š” ์นœ๊ตฌ๋ฅผ ํ†ตํ•ด์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜จ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

// WootechoResponse.java
public static WootechoResponse of(final Wootecho wootecho) {
    return new WootechoResponse(wootecho.getId(), wootecho.getCourse().name());
}

๊ทธ๋ฆฌ๊ณ , WootechoResponse์—์„œ ์‹ค์ œ๋กœ ๊ฐ’์„ ์ฑ„์›Œ๋„ฃ์œผ๋ ค๊ณ  ํ•  ๋•Œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด์„œ ์‹ค์ œ ๊ฐ’์„ ์ฑ„์›Œ๋„ฃ์œผ๋ ค๊ณ  ํ•˜์ž ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค.

 

dto ๋ณ€ํ™˜์„ controller = presentation layer์—์„œ ํ•˜๋ฉด์„œ, ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์„œ๋น„์Šค ๋ ˆ์ด์–ด๋ฅผ ์ง€๋‚˜ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๊ฐ€ ๋๋‚˜๋ฉด์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋„ ํ•จ๊ป˜ ์ œ๊ฑฐ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. no session์ด๋ผ๋Š” ๊ฒƒ์ด ๊ณง EntityManager๊ฐ€ ์—†์–ด์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜๊ฐ€ ์—†๋˜ ๊ฒƒ์ด๋‹ค.

 

service ๋ ˆ์ด์–ด์—์„œ ์กฐํšŒํ•ด์˜จ Crew ์—”ํ‹ฐํ‹ฐ๋Š” '์˜์† ์ƒํƒœ'์˜€์œผ๋‚˜, controller ๋ ˆ์ด์–ด๋กœ ๋„˜์–ด์˜ค๋ฉด์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์ œ๊ฑฐ๋˜์–ด Crew ์—”ํ‹ฐํ‹ฐ๋Š” ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋˜์—ˆ๋‹ค.

 

์‚ฌ์‹ค ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ , ๊ทธ๋ƒฅ ์„œ๋น„์Šค์—์„œ DTO๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

// CrewService.java
public CrewResponse getById(final Long wootechoId) {
    final Crew findCrew = crewRepository.findById(wootechoId)
        .orElseThrow(() -> new IllegalArgumentException("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์•„์ด๋””์ž…๋‹ˆ๋‹ค."));
    return CrewResponse.of(findCrew);
}

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๋ฒ”์œ„๊ฐ€ ์‚ด์•„์žˆ๋Š” ๋ฒ”์œ„์—์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด ์ดˆ๊ธฐํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

 

ํ•˜์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ dto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์€ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ผ๊นŒ?

๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ด๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„์ง€ ์‚ดํŽด๋ณด์ž.

Comments